diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index d5bb421f..a445f873 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -3,24 +3,16 @@ name: build
on:
push:
- branches: [ master ]
tags:
- v*
pull_request:
branches: [ master ]
-# matrix:
-# maya: [2024]
-# os: [macos-latest, ubuntu-latest, windows-latest]
-# include:
-# - maya: 2024
-# update: 2
-
jobs:
compile_plugin:
strategy:
matrix:
- maya: [2022, 2023, 2024, 2025]
+ maya: [2022, 2023, 2024, 2025, 2026]
os: [macos-13, macos-latest, ubuntu-latest, windows-latest]
include:
# Add the maya update versions here
@@ -31,7 +23,9 @@ jobs:
- maya: 2024
update: 2
- maya: 2025
- update: 1
+ update: 3
+ - maya: 2026
+ update: 2
# cross-compiling is annoying so just fall back to macos-13
exclude:
@@ -43,13 +37,18 @@ jobs:
maya: 2024
- os: macos-13
maya: 2025
+ - os: macos-13
+ maya: 2026
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- - uses: actions/checkout@v4
- - run: git fetch --force --tags origin
+ - name: 'Checkout repo'
+ uses: actions/checkout@v5
+
+ - name: 'ACTUALLY get tags'
+ run: git fetch --force --tag
- name: Get Maya Devkit
id: get-devkit
@@ -61,6 +60,7 @@ jobs:
- name: Build Maya
uses: blurstudio/mayaModuleActions/mesonBuild@v1
with:
+ meson-version: 1.9.0
setup-args: >
-Dmaya:maya_version=${{ matrix.maya }}
-Dmaya:maya_devkit_base=${{ steps.get-devkit.outputs.devkit-path }}
@@ -88,39 +88,43 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- - uses: actions/checkout@v4
- - run: git fetch --force --tags origin
+ - name: 'Checkout repo'
+ uses: actions/checkout@v5
+
+ - name: 'ACTUALLY get tags'
+ run: git fetch --force --tags origin
+
- name: 'Get Previous tag'
id: previoustag
uses: "WyriHaximus/github-action-get-previous-tag@v1"
with:
fallback: v0.0.1
+ - name: 'Write version.txt'
+ run: echo "${{ steps.previoustag.outputs.tag }}" > version.txt
+
- name: Get pyver macos-latest
if: ${{ matrix.os == 'macos-latest' }}
shell: bash
run: |
echo "PY_VER=3.9" >> $GITHUB_ENV
echo "PY_VER_FLAT=39" >> $GITHUB_ENV
- echo "PY_EXT=so" >> $GITHUB_ENV
echo "PLAT_TAG=macosx_12_0_arm64" >> $GITHUB_ENV
- name: Get pyver ubuntu-latest
if: ${{ matrix.os == 'ubuntu-latest' }}
shell: bash
run: |
- echo "PY_VER=3.7" >> $GITHUB_ENV
- echo "PY_VER_FLAT=37" >> $GITHUB_ENV
- echo "PY_EXT=so" >> $GITHUB_ENV
+ echo "PY_VER=3.9" >> $GITHUB_ENV
+ echo "PY_VER_FLAT=39" >> $GITHUB_ENV
echo "PLAT_TAG=manylinux_2_17_x86_64" >> $GITHUB_ENV
- name: Get pyver windows-latest
if: ${{ matrix.os == 'windows-latest' }}
shell: bash
run: |
- echo "PY_VER=3.7" >> $GITHUB_ENV
- echo "PY_VER_FLAT=37" >> $GITHUB_ENV
- echo "PY_EXT=pyd" >> $GITHUB_ENV
+ echo "PY_VER=3.9" >> $GITHUB_ENV
+ echo "PY_VER_FLAT=39" >> $GITHUB_ENV
echo "PLAT_TAG=win_amd64" >> $GITHUB_ENV
- name: Get an older python version
@@ -128,35 +132,20 @@ jobs:
with:
python-version: ${{ env.PY_VER }}
- - name: Build Python
- uses: blurstudio/mayaModuleActions/mesonBuild@v1
- with:
- setup-args: >
- -Dmaya_build=false
- -Dpython_build=true
- --buildtype release
- --backend ninja
- install-args: --skip-subprojects
+ - name: Install pip packages
+ shell: bash
+ run: |
+ python -m pip install -U build wheel
- name: Build Wheel
shell: bash
run: |
- python -m pip install -U pip
- python -m pip install -U build wheel hatch
- python -m hatch version ${{ steps.previoustag.outputs.tag }}
python -m build --wheel
for PY_WHEEL in dist/*.whl
do
python -m wheel tags --remove --python-tag ${{ env.PY_VER_FLAT }} --abi-tag abi3 --platform-tag ${{ env.PLAT_TAG }} ${PY_WHEEL}
done
- - name: Upload Artifacts
- uses: actions/upload-artifact@v4
- with:
- name: ${{ runner.os }}-pyModule
- path: output_Python/*.${{ env.PY_EXT }}
- if-no-files-found: error
-
- name: Upload Artifacts
uses: actions/upload-artifact@v4
with:
@@ -169,14 +158,28 @@ jobs:
needs: [compile_plugin, compile_python]
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
- - run: git fetch --force --tags origin
- - name: 'Get Previous tag'
+ - name: Checkout repo
+ uses: actions/checkout@v5
+
+ - name: 'ACTUALLY get tags'
+ run: git fetch --force --tags origin
+
+ - name: Get Previous tag
id: previoustag
uses: "WyriHaximus/github-action-get-previous-tag@v1"
with:
fallback: v0.0.1
+ - name: Build the module scripts folder
+ uses: blurstudio/mayaModuleActions/mesonBuild@v1
+ with:
+ meson-version: 1.9.0
+ setup-args: >
+ -Dmaya_build=false
+ -Dpython_build=true
+ -Dpython_script_build=true
+ install-args: --skip-subprojects
+
- name: Package
uses: blurstudio/mayaModuleActions/packageMayaModule@v1
with:
diff --git a/.gitignore b/.gitignore
index dfa65758..125cd6dc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,13 @@ ehthumbs.db
# Release zipfile
Simplex.zip
+# Auto-generated version files
+version.txt
+_version.py
+
+# clang compile commands
+compile_commands.json
+
# Build folders
Maya20*/
**/build/
@@ -38,6 +45,17 @@ $RECYCLE.BIN/
# Windows shortcuts
*.lnk
+# clang LSP
+.cache
+
+# Known subprojects
+**/Eigen-*/
+**/rapidjson-*/
+subprojects/packagecache/
+
+# python wheel dist folder
+dist/
+
# =========================
# Operating System Files
# =========================
diff --git a/README.md b/README.md
index 5ca319cb..48112203 100644
--- a/README.md
+++ b/README.md
@@ -63,4 +63,4 @@ runSimplexUI()
## Compiling
-Hopefully you don't need to do this, but if you have to, just take a look at `.github/workflows/main.yml` and you should be able to piece together how to get a compile working using CMake. You aren't required to download the devkit or set its path for CMake if you've got maya installed on your machine. Also note, I use features from CMake 3.16+ so I can target python 2 and 3 separately.
+Hopefully you don't need to do this, but if you have to, just take a look at the `quick_compile.bat` file to see how to kick off a compile. And also look at `.github/workflows/main.yml` if you need to piece together the dependencies required.
diff --git a/get_version.py b/get_version.py
new file mode 100644
index 00000000..b19a9045
--- /dev/null
+++ b/get_version.py
@@ -0,0 +1,38 @@
+import os
+import subprocess
+import sys
+
+VERSION_FILE = "version.txt"
+
+
+def main(srcroot, gitpath):
+ """
+ When building a python wheel, the build system copies the entire source tree into a new folder
+ but it doesn't seem to include git tags, or it doesn't check out the git tags, or *something*
+ So I'm writing out a version.txt file that this function will read and pass along to the
+ meson project() function call
+ """
+ # If version.txt already exists, use it
+ src_file = os.path.join(srcroot, VERSION_FILE)
+ if os.path.exists(src_file):
+ with open(src_file) as f:
+ v = f.read().strip()
+ else:
+ # Otherwise, try git describe
+ try:
+ # fmt: off
+ v = subprocess.check_output(
+ [gitpath, "describe", "--tags", "--always", "--match", "v[0-9]*", "--abbrev=0"],
+ text=True,
+ ).strip()
+ # fmt: on
+ except Exception:
+ v = "0.0.1" # fallback if not in a git repo
+
+ print(v)
+
+
+if __name__ == "__main__":
+ srcroot = sys.argv[1]
+ gitpath = sys.argv[2]
+ sys.exit(main(srcroot, gitpath))
diff --git a/meson.build b/meson.build
index 52228c36..767f3972 100644
--- a/meson.build
+++ b/meson.build
@@ -1,5 +1,19 @@
-project('Simplex', 'cpp', default_options: ['cpp_std=c++20'])
-
+project(
+ 'Simplex',
+ 'cpp',
+ meson_version : '>=1.7.0',
+ version : configure_file(
+ capture : true,
+ command : [
+ find_program('python', required: true),
+ meson.project_source_root() / 'get_version.py',
+ meson.project_source_root(),
+ find_program('git', native: true, required: true).full_path(),
+ ],
+ output : 'version.txt',
+ ),
+ default_options: ['cpp_std=c++20'],
+)
maya_build = get_option('maya_build')
python_build = get_option('python_build')
@@ -8,6 +22,9 @@ if not maya_build and not python_build
error('No builds requested')
endif
+conf_data = configuration_data()
+conf_data.set('VCS_TAG', meson.project_version())
+
subdir('src/simplexlib')
if maya_build
maya_dep = dependency('maya')
diff --git a/meson.options b/meson.options
index 17700b32..36793e6c 100644
--- a/meson.options
+++ b/meson.options
@@ -1,2 +1,4 @@
option('maya_build', type : 'boolean', value : true)
option('python_build', type : 'boolean', value : true)
+option('python_wheel_build', type : 'boolean', value : false)
+option('python_script_build', type : 'boolean', value : false)
diff --git a/pyproject.toml b/pyproject.toml
index f83b8bf7..f469cf6c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,12 +1,8 @@
[project]
name = "simplex-solver"
-dynamic = [
- "version"
-]
+dynamic = ["version"]
-authors = [
- { name="Tyler Fox", email="tyler@blur.com" },
-]
+authors = [{ name = "Tyler Fox", email = "tyler@blur.com" }]
description = "The Blur Studio Simplex Blendshape Combination System"
readme = "README.md"
requires-python = ">=3.7"
@@ -22,36 +18,27 @@ classifiers = [
"Operating System :: MacOS :: MacOS X",
]
-dependencies = [
- "six",
- "Qt.py",
-]
+dependencies = ["Qt.py"]
[project.urls]
"Project Page" = "https://github.com/blurstudio/simplex"
-[build-system]
-requires = ["hatchling"]
-build-backend = "hatchling.build"
-
-[tool.hatch.build.targets.wheel.sources]
-"scripts" = ""
-"output_Python" = ""
-
-[tool.hatch.build.targets.wheel]
-only-packages = false
-artifacts = [
- "*.so",
- "*.pyd",
- "!*.lib",
-]
-only-include = [
- "scripts/simplexui",
- "output_Python",
+[project.optional-dependencies]
+dev = [
+ "tox",
+ "covdefaults",
+ "coverage",
+ "ruff",
]
-[tool.hatch.version]
-path = "scripts/simplexui/__init__.py"
+
+[build-system]
+build-backend = "mesonpy"
+requires = ["meson-python>=0.15.0", "meson >= 1.6.0"]
+
+[tool.meson-python.args]
+setup = ['-Dmaya_build=false', '-Dpython_build=true', '-Dpython_wheel_build=true']
+install = ['--skip-subprojects']
[tool.ruff]
# Exclude a variety of commonly ignored directories.
@@ -88,22 +75,15 @@ exclude = [
"shared-venv",
"site-packages",
"venv",
+ "subprojects",
]
-line-length = 100
+line-length = 88
indent-width = 4
target-version = "py37"
[tool.ruff.lint]
-select = [
- "B",
- "C",
- "E",
- "F",
- "N",
- "W",
- "B9",
-]
+select = ["B", "C", "E", "F", "W", "B9"]
ignore = [
"B905",
"C901",
@@ -126,7 +106,7 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
[tool.ruff.format]
# Like Black, use double quotes for strings.
-quote-style = "double"
+quote-style = "preserve"
# Like Black, indent with spaces, rather than tabs.
indent-style = "space"
@@ -136,4 +116,3 @@ skip-magic-trailing-comma = false
# Like Black, automatically detect the appropriate line ending.
line-ending = "auto"
-
diff --git a/quick_compile.bat b/quick_compile.bat
index 0e2b3944..9bade032 100644
--- a/quick_compile.bat
+++ b/quick_compile.bat
@@ -10,11 +10,16 @@ SET BUILDTYPE=release
SET BUILDDIR=mayabuild_%BUILDTYPE%_%MAYA_VERSION%_%BACKEND%
if not exist %BUILDDIR%\ (
- meson setup %BUILDDIR% -Dmaya:maya_version=%MAYA_VERSION% --buildtype %BUILDTYPE% --vsenv --backend %BACKEND%
+ meson setup %BUILDDIR% ^
+ -Dmaya:maya_version=%MAYA_VERSION% ^
+ -Dmaya_build=true ^
+ -Dpython_build=false ^
+ -Dpython_script_build=false ^
+ --buildtype %BUILDTYPE% --vsenv --backend %BACKEND%
)
if exist %BUILDDIR%\ (
- meson compile -C %BUILDDIR%
+ meson compile -C %BUILDDIR% -j 8
meson install --skip-subprojects -C %BUILDDIR%
)
diff --git a/scripts/simplexui/commands/buildIceXML.py b/scripts/simplexui/commands/buildIceXML.py
deleted file mode 100644
index 9b0f6e51..00000000
--- a/scripts/simplexui/commands/buildIceXML.py
+++ /dev/null
@@ -1,443 +0,0 @@
-# Copyright 2016, Blur Studio
-#
-# This file is part of Simplex.
-#
-# Simplex is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Simplex is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with Simplex. If not, see .
-
-BASEXML = """
-
-\t
-\t\t
-{0}
-\t\t
-\t\t
-\t\t\t
-\t\t\t
-\t\t
-\t\t
-{1}
-\t\t
-\t\t
-\t\t\t
-\t\t\t-
-\t\t
-\t
-"""
-
-
-SLIDERBASEXML = """
-
-\t
-\t\t
-{0}
-\t\t
-\t\t
-\t\t\t
-\t\t
-\t\t
-{1}
-\t\t
-\t\t
-\t\t\t-
-\t\t
-\t
-"""
-
-GETDATANODE = """
-\t\t\t
-\t\t\t\t
-\t\t\t\t
-\t\t\t\t
-\t\t\t\t
-\t\t\t\t
-\t\t\t\t\t0
-\t\t\t\t\t0
-\t\t\t\t
-\t\t\t
-"""
-MULNODE = """
-\t\t\t
-\t\t\t\t
-\t\t\t\t
-\t\t\t\t
-\t\t\t\t\t0
-\t\t\t\t\t0
-\t\t\t\t
-\t\t\t
-"""
-
-BUILDARRAYNODE = """
-\t\t\t
-{0}
-\t\t\t
-"""
-
-ADDNODE = """
-\t\t\t
-{0}
-\t\t\t
-"""
-
-SELECTINARRAYNODE = """
-\t\t\t
-\t\t\t\t
-\t\t\t\t
-\t\t\t\t
-\t\t\t\t
-\t\t\t\t
-\t\t\t\t\t0
-\t\t\t\t\t0
-\t\t\t\t
-\t\t\t
-"""
-
-PASSTHROUGHNODE = """
-\t\t\t
-\t\t\t\t
-\t\t\t\t
-\t\t\t\t
-\t\t\t\t\t0
-\t\t\t\t\t0
-\t\t\t\t
-\t\t\t
-"""
-
-ADDPORT = '\t\t\t\t'
-SCALARPORT = '\t\t\t\t'
-CONNECTION = (
- '\t\t\t '
-)
-OUTPROPERTY = "self.{0}_outProperty.{1}"
-POSPROPERTY = "self.cls.{0}.{1}.positions"
-INPROPERTY = "self.{0}_inProperty.{1}"
-
-
-LOADER = """
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-"""
-
-
-BASEDEBUGXML = """
-
-\t
-\t\t
-{0}
-\t\t
-\t\t
-\t\t\t
-\t\t\t
-\t\t
-\t\t
-{1}
-\t\t
-\t\t
-\t\t\t-
-\t\t\t-
-\t\t
-\t
-"""
-
-EXECUTENODE = """
-\t\t\t
-{1}
-\t\t\t\t
-\t\t\t\t\t0
-\t\t\t\t\t0
-\t\t\t\t
-\t\t\t
-"""
-EXECPORT = '\t\t\t\t'
-
-SETDATANODE = """
-\t\t\t
-\t\t\t\t
-\t\t\t\t
-\t\t\t\t
-\t\t\t\t
-\t\t\t\t
-\t\t\t\t\t0
-\t\t\t\t\t0
-\t\t\t\t
-\t\t\t
-"""
-
-
-def buildIceDebugXML(shapeList, systemName, namePrefix):
- """
-
- Parameters
- ----------
- shapeList :
-
- systemName :
-
- namePrefix :
-
-
- Returns
- -------
-
- """
- execNodeIdx = 0
- passIdx = 1
- passNode = PASSTHROUGHNODE.format(passIdx)
- index = 2
-
- execPortIdx = 1
- execPorts = []
- nodes = []
- connections = []
- nodes.append(passNode)
-
- for i, shape in enumerate(shapeList):
- sliIdx = index
- setIdx = index + 1
- index += 2
-
- outPropName = OUTPROPERTY.format(systemName, namePrefix + shape)
- sliNode = SELECTINARRAYNODE.format(sliIdx, i)
- setNode = SETDATANODE.format(setIdx, outPropName)
-
- nodes.append(sliNode)
- nodes.append(setNode)
-
- setCnx = CONNECTION.format(sliIdx, "value", setIdx, "source")
- execCnx = CONNECTION.format(
- setIdx, "value", execNodeIdx, "port" + str(execPortIdx)
- )
- passCnx = CONNECTION.format(passIdx, "out", sliIdx, "array")
-
- connections.append(setCnx)
- connections.append(execCnx)
- connections.append(passCnx)
-
- execPort = EXECPORT.format(execPortIdx)
- execPorts.append(execPort)
- execPortIdx += 1
-
- allExecPorts = "\n".join(execPorts)
- execNode = EXECUTENODE.format(execNodeIdx, allExecPorts)
- nodes.insert(execNodeIdx, execNode)
- allNodes = "\n".join(nodes)
- allConnections = "\n".join(connections)
- output = BASEDEBUGXML.format(allNodes, allConnections)
- return output
-
-
-def buildIceXML(shapeList, systemName, clusterName, namePrefix):
- """
-
- Parameters
- ----------
- shapeList :
-
- systemName :
-
- clusterName :
-
- namePrefix :
-
-
- Returns
- -------
-
- """
- index = 1
- addIdx = 1
-
- addPorts = []
- nodes = []
- connections = []
-
- passNode = PASSTHROUGHNODE.format(index)
- nodes.append(passNode)
-
- index += 1
-
- selfPos = GETDATANODE.format(index, "Self.PointPosition")
- selfCnx = CONNECTION.format(index, "value", 0, "value" + str(addIdx))
- addPort = ADDPORT.format(addIdx, addIdx - 1)
-
- nodes.append(selfPos)
- connections.append(selfCnx)
- addPorts.append(addPort)
-
- addIdx += 1
- index += 1
-
- for i, shape in enumerate(shapeList):
- sliIdx = index
- posIdx = index + 1
- mulIdx = index + 2
- index += 3
-
- posPropName = POSPROPERTY.format(clusterName, namePrefix + shape)
-
- sliNode = SELECTINARRAYNODE.format(sliIdx, i)
- posNode = GETDATANODE.format(posIdx, posPropName)
- mulNode = MULNODE.format(mulIdx)
-
- nodes.append(sliNode)
- nodes.append(posNode)
- nodes.append(mulNode)
-
- sliCnx = CONNECTION.format(sliIdx, "value", mulIdx, "factor")
- connections.append(sliCnx)
-
- posCnx = CONNECTION.format(posIdx, "value", mulIdx, "value")
- connections.append(posCnx)
-
- passCnx = CONNECTION.format(1, "out", sliIdx, "array")
- connections.append(passCnx)
-
- if i != 0:
- # Don't connect the rest shape
- addCnx = CONNECTION.format(mulIdx, "result", 0, "value" + str(addIdx))
- connections.append(addCnx)
- addPort = ADDPORT.format(addIdx, addIdx - 1)
- addPorts.append(addPort)
- addIdx += 1
-
- allPorts = "\n".join(addPorts)
- addNode = ADDNODE.format(allPorts)
- allNodes = "\n".join(nodes)
- allNodes = addNode + "\n" + allNodes
- allConnections = "\n".join(connections)
- output = BASEXML.format(allNodes, allConnections)
-
- return output
-
-
-def buildSliderIceXML(sliderList, systemName):
- """
-
- Parameters
- ----------
- sliderList :
-
- systemName :
-
-
- Returns
- -------
-
- """
- index = 1
- addIdx = 1
-
- sliderPorts = []
- nodes = []
- connections = []
-
- for slider in sliderList:
- inIdx = index
-
- inPropName = INPROPERTY.format(systemName, slider)
-
- inNode = GETDATANODE.format(inIdx, inPropName)
-
- nodes.append(inNode)
-
- sliderCnx = CONNECTION.format(inIdx, "value", 0, "value" + str(addIdx))
- connections.append(sliderCnx)
-
- sliderPort = SCALARPORT.format(addIdx, addIdx - 1)
- sliderPorts.append(sliderPort)
-
- index += 1
- addIdx += 1
-
- allPorts = "\n".join(sliderPorts)
- arrayNode = BUILDARRAYNODE.format(allPorts)
- allNodes = "\n".join(nodes)
- allNodes = arrayNode + "\n" + allNodes
- allConnections = "\n".join(connections)
- output = SLIDERBASEXML.format(allNodes, allConnections)
-
- return output
-
-
-def buildLoaderXML(loader, rester):
- """
-
- Parameters
- ----------
- loader :
-
- rester :
-
-
- Returns
- -------
-
- """
- return LOADER.format(loader.name, rester.name)
diff --git a/scripts/simplexui/commands/setDelta.xsicompound b/scripts/simplexui/commands/setDelta.xsicompound
deleted file mode 100644
index 04469390..00000000
--- a/scripts/simplexui/commands/setDelta.xsicompound
+++ /dev/null
@@ -1,60 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
diff --git a/scripts/simplexui/commands/xsiCorrectiveInterface.py b/scripts/simplexui/commands/xsiCorrectiveInterface.py
deleted file mode 100644
index c760234d..00000000
--- a/scripts/simplexui/commands/xsiCorrectiveInterface.py
+++ /dev/null
@@ -1,136 +0,0 @@
-# Copyright 2016, Blur Studio
-#
-# This file is part of Simplex.
-#
-# Simplex is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Simplex is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with Simplex. If not, see .
-
-""" Get the corrective deltas from a rig in XSI """
-
-from __future__ import absolute_import
-
-from dcc.xsi import constants, xsi
-from dcc.xsi.ice import ICETree # pylint:disable=import-error
-from six.moves import zip
-
-
-def setPose(pvp, multiplier):
- """Set a percentage of a pose
-
- Parameters
- ----------
- pvp : [(str, float), ...]
- A list of property/value pairs
- multiplier : float
- The percentage multiplier of the pose
- """
- for prop, val in pvp:
- xsi.setValue(prop, val * multiplier)
-
-
-def resetPose(pvp):
- """Reset everything back to rest
-
- Parameters
- ----------
- pvp : [(str, float), ...]
- A list of property/value pairs
- """
- for prop, val in pvp:
- xsi.setValue(prop, 0)
-
-
-def getMeshVerts(mesh):
- """Get the verts of the given mesh object
-
- Parameters
- ----------
- mesh : XSIObject
- A native xsi mesh object
-
- Returns
- -------
- : [vert, ...]
- A list of vertices
- """
- vts = mesh.ActivePrimitive.Geometry.Points.PositionArray
- return list(zip(*vts))
-
-
-def buildTree(mesh):
- """Build the ICE tree that allows for this procedure
-
- Parameters
- ----------
- mesh : XSIObject
- A native xsi mesh object
-
- Returns
- -------
- : XSIObject
- The ICE Tree object
- : XSIObject
- The vector node in the ICE Tree
- """
- iceTree = ICETree(None, mesh, "Test", constants.siConstructionModePrimaryShape)
-
- getter = iceTree.addGetDataNode("Self.PointPosition")
- adder = iceTree.addNode("Add")
- vector = iceTree.addNode("ScalarTo3DVector")
- setter = iceTree.addSetDataNode("Self.PointPosition")
-
- getter.value.connect(adder.value1)
- vector.vector.connect(adder.value2)
- adder.result.connect(setter.Value)
- iceTree.connect(setter.Execute, 2)
-
- return iceTree, vector
-
-
-def getShiftValues(mesh):
- """Shift the vertices along each axis *before* the skinning
- op in the deformer history
-
- Parameters
- ----------
- mesh : XSIObject
- A native xsi mesh object
-
- Returns
- -------
- : [vert, ...]
- A list of un-shifted vertices
- : [vert, ...]
- A list of vertices pre-shifted by 1 along the X axis
- : [vert, ...]
- A list of vertices pre-shifted by 1 along the Y axis
- : [vert, ...]
- A list of vertices pre-shifted by 1 along the Z axis
- """
- tree, vector = buildTree(mesh)
- zero = getMeshVerts(mesh)
-
- vector.x.value = 1.0
- oneX = getMeshVerts(mesh)
- vector.x.value = 0.0
-
- vector.y.value = 1.0
- oneY = getMeshVerts(mesh)
- vector.y.value = 0.0
-
- vector.z.value = 1.0
- oneZ = getMeshVerts(mesh)
- vector.z.value = 0.0
-
- tree.delete()
- return zero, oneX, oneY, oneZ
diff --git a/scripts/simplexui/interface/xsiInterface.py b/scripts/simplexui/interface/xsiInterface.py
deleted file mode 100644
index 7d0fae86..00000000
--- a/scripts/simplexui/interface/xsiInterface.py
+++ /dev/null
@@ -1,2944 +0,0 @@
-# Copyright 2016, Blur Studio
-#
-# This file is part of Simplex.
-#
-# Simplex is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Simplex is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with Simplex. If not, see .
-
-
-# pylint: disable=invalid-name
-from __future__ import absolute_import, print_function
-
-import json
-import os
-import tempfile
-from contextlib import contextmanager
-from functools import wraps
-from itertools import repeat
-
-import dcc.xsi as dcc
-import numpy as np
-import six
-from alembic.AbcGeom import OPolyMeshSchemaSample
-from imath import IntArray, V3f, V3fArray
-from six.moves import range, zip
-
-from ..commands.alembicCommon import mkUvSample
-from ..commands.buildIceXML import buildIceXML, buildLoaderXML, buildSliderIceXML
-from Qt import QtCore
-from Qt.QtCore import Signal
-from Qt.QtWidgets import QApplication, QDialog, QMainWindow, QSplashScreen
-
-
-# UNDO STACK INTEGRATION
-@contextmanager
-def undoContext(inst=None):
- """
-
- Parameters
- ----------
- inst :
- (Default value = None)
-
- Returns
- -------
-
- """
- if inst is None:
- DCC.staticUndoOpen()
- else:
- inst.undoOpen()
- try:
- yield
- finally:
- if inst is None:
- DCC.staticUndoClose()
- else:
- inst.undoClose()
-
-
-def undoable(f):
- """
-
- Parameters
- ----------
- f :
-
-
- Returns
- -------
-
- """
-
- @wraps(f)
- def stacker(*args, **kwargs):
- """
-
- Parameters
- ----------
- *args :
-
- **kwargs :
-
-
- Returns
- -------
-
- """
- with undoContext():
- return f(*args, **kwargs)
-
- return stacker
-
-
-# temporarily disconnect inputs from the slider list
-@contextmanager
-def disconnected(sliders, prop):
- """
-
- Parameters
- ----------
- sliders :
-
- prop :
-
-
- Returns
- -------
-
- """
- params = [slider.thing for slider in sliders]
- pairs = []
- toRemove = []
- for param in params:
- if param.IsAnimated(dcc.constants.siExpressionSource):
- pairs.append((param.Name, param.Source.Definition.Value))
-
- toRemove.append(param)
- dcc.xsi.RemoveAnimation(toRemove)
- dcc.xsi.SetValue(params, 0.0)
-
- try:
- yield pairs
- finally:
- for paramName, exp in pairs:
- param = prop.Parameters(paramName)
- if param is not None:
- param.AddExpression(exp)
-
-
-class DCC(object):
- """ """
-
- program = "xsi"
- shapeNamePrefix = ""
- texName = "Texture_Projection"
-
- def __init__(self, simplex, stack=None):
- self.name = None # the name of the system
- self.mesh = None # the mesh object with the system
- self.inProp = None # the control property on the object
- self.shapeCluster = None # the cluster containing the shapes
- self.shapeTree = None # the ICETree driving the shapes
- self.shapeNode = None # the shape compound node in the IceTree
- self.sliderNode = None # the slider compound node in the IceTree
- self.op = None # the simplex node in the IceTree
- self.simplex = simplex # the abstract representation of the setup
- self.undoDepth = 0
- self._live = True
- self.sliderMul = self.simplex.sliderMul
-
- cls = type(self)
- self.shapeNamePrefix = cls.shapeNamePrefix
- # Reset the prefix because othewise, someone will forget
- cls.shapeNamePrefix = ""
-
- def getShapeThing(self, shapeName):
- """
-
- Parameters
- ----------
- shapeName :
-
-
- Returns
- -------
-
- """
- for prop in self.shapeCluster.Properties:
- if prop.Name == self.shapeNamePrefix + shapeName:
- return [prop, None, None, None]
- return None
-
- def getSliderThing(self, sliderName):
- """
-
- Parameters
- ----------
- sliderName :
-
-
- Returns
- -------
-
- """
- return self.inProp.Parameters(sliderName)
-
- def preLoad(self, simp, simpDict, create=True, pBar=None):
- """
-
- Parameters
- ----------
- simp :
-
- simpDict :
-
- create :
- (Default value = True)
- pBar :
- (Default value = None)
-
- Returns
- -------
-
- """
- # Pre-build all the nodes and parameters quickly
- if pBar is not None:
- pBar.setLabelText("Loading Connections")
- QApplication.processEvents()
- ev = simpDict["encodingVersion"]
-
- shapeNames = simpDict.get("shapes")
- if not shapeNames:
- return
-
- if ev > 1:
- shapeNames = [i["name"] for i in shapeNames]
-
- # Gather all the missing shapes to create in one go
- if pBar is not None:
- pBar.setMaximum(len(shapeNames))
- pBar.setLabelText("Checking for missing shapes")
- pBar.setValue(0)
- QApplication.processEvents()
-
- toMake = self._checkAllShapeValidity(shapeNames, pBar=pBar)
-
- if toMake:
- # Make 1 master duplicate and store it as a shapekey
- # This ensures that all the shapes are created on the correct cluster
- dupName = "__Simplex_Master_Dup"
- tempVertArray, tempFaceArray = self.mesh.ActivePrimitive.Geometry.Get2()
- dup = self.mesh.parent.AddPolygonMesh(tempVertArray, tempFaceArray, dupName)
- dup.Properties("Visibility").viewvis = False
-
- newShape = dcc.xsi.StoreShapeKey(
- self.shapeCluster,
- dup.Name,
- dcc.constants.siShapeObjectReferenceMode,
- 1,
- 0,
- 0,
- dcc.constants.siShapeContentPrimaryShape,
- False,
- )
- dcc.xsi.FreezeObj(newShape)
- dcc.xsi.DeleteObj(dup)
- sks = [newShape]
-
- if len(toMake) > 1:
- # If there are more shapes to make, then we make them by
- # duplicating the shapes from the mixer. This is easily
- # 50x faster than creating shapes via any other method
- if pBar is not None:
- pBar.setMaximum(len(toMake))
- pBar.setValue(0)
- pBar.setLabelText("Creating Empty Shapes")
- QApplication.processEvents()
-
- mixShape = "{0}.{1}".format(
- newShape.model.mixer.FullName, newShape.Name
- )
- # This may fail on stupid-dense meshes.
- # Maybe add a pointCount vs. chunk size heuristic?
- chunk = 20
- for i in range(0, len(toMake[1:]), chunk):
- if pBar is not None:
- pBar.setValue(i)
- QApplication.processEvents()
-
- curSize = min(len(toMake) - 1, i + chunk) - i
- # Can't duplicate more than 1 at a time, otherwise we get memory issues
- dupShapes = dcc.xsi.Duplicate(
- mixShape,
- curSize,
- dcc.constants.siCurrentHistory,
- dcc.constants.siSharedParent,
- dcc.constants.siShareGrouping,
- dcc.constants.siNoProperties,
- dcc.constants.siDuplicateAnimation,
- dcc.constants.siShareConstraints,
- dcc.constants.siNoSelection,
- )
- for dd in dupShapes:
- cs = dcc.xsi.GetValue(
- "{0}.{1}".format(self.shapeCluster.FullName, dd.Name)
- )
- sks.append(cs)
-
- if pBar is not None:
- pBar.setLabelText("Naming Shapes")
- pBar.setValue(0)
- pBar.setMaximum(len(sks))
- QApplication.processEvents()
-
- with self.noShapeNode():
- chunk = 20
- pfxToMake = [self.shapeNamePrefix + i for i in toMake]
- for idx in range(0, len(sks), chunk):
- if pBar is not None:
- pBar.setValue(idx)
- pns = ",".join(
- [p.fullname + ".Name" for p in sks[idx : idx + chunk]]
- )
- dcc.xsi.SetValue(pns, pfxToMake[idx : idx + chunk])
-
- dcc.xsi.FreezeObj(
- [i for i in self.shapeCluster.Properties if len(i.NestedObjects) > 2]
- )
-
- clsCombiner = dcc.operator.getOperatorFromStack(
- self.mesh, "clustershapecombiner"
- )
- if clsCombiner:
- dcc.xsi.DeleteObj(clsCombiner)
-
- sliderNames = simpDict.get("sliders")
- if not sliderNames:
- return
- if ev > 1:
- sliderNames = [i["name"] for i in sliderNames]
- else:
- sliderNames = [i[0] for i in sliderNames]
-
- for sliderName in sliderNames:
- sliderParam = self.inProp.Parameters(sliderName)
- if not sliderParam:
- if not create:
- raise RuntimeError(
- "Slider {0} not found with creation turned off".format(
- sliderName
- )
- )
- self.inProp.AddParameter3(
- sliderName, dcc.constants.siFloat, 0, -2.0, 2.0
- )
- return None
-
- def postLoad(self, simp, preRet):
- """
-
- Parameters
- ----------
- simp :
-
- preRet :
-
-
- Returns
- -------
-
- """
- self.rebuildSliderNode()
- self.resetShapeIndexes()
- self.recreateShapeNode()
- self.updateSlidersRange(simp.sliders)
-
- def checkForErrors(self, window):
- """ Check for any DCC specific errors
-
- Parameters
- ----------
- window : QMainWindow
- The simplex window
- """
- pass
-
- # System IO
- @undoable
- def loadNodes(self, simp, thing, create=True, pBar=None):
- """Create a new system based on the simplex tree
- Build any DCC objects that are missing if create=True
- Raises a runtime error if missing objects are found and
- create=False
-
- Parameters
- ----------
- simp :
-
- thing :
-
- create :
- (Default value = True)
- pBar :
- (Default value = None)
-
- Returns
- -------
-
- """
- self.name = simp.name
- self.mesh = thing
-
- # find/build the shapeCluster
- if pBar is not None:
- pBar.setLabelText("Loading Nodes")
- QApplication.processEvents()
-
- shapeCluster = thing.ActivePrimitive.Geometry.Clusters("Shape")
- if not shapeCluster:
- shapeCluster = thing.ActivePrimitive.Geometry.Clusters(
- "%s_Shapes" % self.name
- )
- if not shapeCluster:
- if not create:
- raise RuntimeError("Shape cluster not found with creation turned off")
- self.shapeCluster = dcc.xsi.CreateCluster("%s.pnt[*]" % thing.FullName)[0]
- self.shapeCluster.Name = "%s_Shapes" % self.name
- dcc.xsi.SelectObj(thing)
- else:
- self.shapeCluster = shapeCluster
-
- # find/build the Icetree
- shapeTree = thing.ActivePrimitive.ICETrees("%s_IceTree" % self.name)
- if not shapeTree:
- if not create:
- raise RuntimeError(
- "Simplex shape ICETree not found with creation turned off"
- )
- self.shapeTree = dcc.ice.ICETree(
- None,
- self.mesh,
- "%s_IceTree" % self.name,
- dcc.constants.siConstructionModePrimaryShape,
- )
- else:
- self.shapeTree = dcc.ice.ICETree(shapeTree)
-
- # find/build the Slider compound in the Icetree
- sliderComp = None
- for node in self.shapeTree.compoundNodes:
- if node.name == "SliderArray":
- sliderComp = node
- break
- if not sliderComp:
- if not create:
- raise RuntimeError(
- "Slider array compound not found in ICETree with creation turned off"
- )
- sliderArray = self.shapeTree.addNode("BuildArray")
- sliderComp = self.shapeTree.createCompound([sliderArray])
- sliderComp.rename("SliderArray")
- sliderComp.exposePort(sliderArray.outputPorts["array"])
- self.sliderNode = sliderComp
-
- # find/build the simplex node in the Icetree
- ops = DCC.getSimplexOperatorsOnObjectByName(thing, self.name)
- if not ops:
- if not create:
- raise RuntimeError("Simplex Operator not found with create turned off")
- op = dcc.ice.ICENode(
- dcc.xsi.AddICENode("SimplexNode", self.shapeTree.fullName)
- )
- op.inputPorts["Sliders"].connect(self.sliderNode.outputPorts["Array"])
- setter = self.shapeTree.addSetDataNode("Self._%s_SimplexVector" % self.name)
- setter.Value.connect(op.outputPorts["Weights"])
- self.shapeTree.connect(setter.Execute, 1)
- self.op = op
- else:
- self.op = ops
-
- # find/build the shape compound in the Icetree
- shapeNode = None
- for node in self.shapeTree.compoundNodes:
- if node.name == "ShapeCompound":
- shapeNode = node
- break
- if not shapeNode:
- if not create:
- raise RuntimeError(
- "Shape compound not found in ICETree with creation turned off"
- )
- setter = self.shapeTree.addSetDataNode("Self.PointPosition")
- self.shapeTree.connect(setter.Execute)
- shapeCompound = self.rebuildShapeNode(simp)
- getter = self.shapeTree.addGetDataNode("Self._%s_SimplexVector" % self.name)
- getter.value.connect(shapeCompound.inputPorts["In"])
- setter.Value.connect(shapeCompound.Result)
- self.shapeNode = shapeCompound
- else:
- self.shapeNode = shapeNode
-
- # find/build the input and output properties
- inProp = thing.Properties("%s_inProperty" % self.name)
- if not inProp:
- if not create:
- raise RuntimeError(
- "Control parameters not found with creation turned off"
- )
- self.inProp = self.mesh.AddProperty(
- "CustomProperty", False, "%s_inProperty" % self.name
- )
- else:
- self.inProp = inProp
-
- def _checkAllShapeValidity(self, shapeNames, errorOnMissing=False, pBar=None):
- """Check shapes to see if they exist, and either gather the missing files, or
- Load the proper data onto the shapes
-
- Parameters
- ----------
- shapeNames :
-
- errorOnMissing :
- (Default value = False)
- pBar :
- (Default value = None)
-
- Returns
- -------
-
- """
- propByName = {i.Name: i for i in list(self.shapeCluster.Properties)}
-
- if pBar is not None:
- pBar.setMaximum(len(shapeNames))
- if errorOnMissing:
- pBar.setLabelText("Checking Validity")
- else:
- pBar.setLabelText("Checking For Missing Shapes")
- pBar.setValue(0)
- QApplication.processEvents()
-
- # Keep the set ordered, but make a set for quick checking
- missingNameSet = set()
- missingNames = []
- seen = set()
-
- for i, shapeName in enumerate(shapeNames):
- if shapeName in seen:
- continue
- seen.add(shapeName)
-
- if pBar is not None:
- pBar.setValue(i)
- QApplication.processEvents()
-
- if self.shapeNamePrefix + shapeName not in propByName:
- if errorOnMissing:
- raise RuntimeError("Missing shape: {}".format(shapeName))
- else:
- if shapeName not in missingNameSet:
- missingNameSet.add(shapeName)
- missingNames.append(shapeName)
- return missingNames
-
- def _loadShapeIceNodes(self, simp):
- """load the ICE nodes related to the given shapeKey
-
- Parameters
- ----------
- simp :
-
-
- Returns
- -------
-
- """
- # Ice nodes are created in-order by the buildIceXML function
- # so we can take advantage of that to properly connect the shapes
- simpNode = self.shapeNode
- children = simpNode.nodes
- # addNode, passNode, getSelfPos = children[:3]
- shapeNodes = children[3:]
- propByName = {i.Name: i for i in list(self.shapeCluster.Properties)}
-
- for i, shape in enumerate(simp.shapes):
- selector = shapeNodes[(3 * i) + 0]._nativePointer
- getData = shapeNodes[(3 * i) + 1]._nativePointer
- multiplier = shapeNodes[(3 * i) + 2]._nativePointer
- shape.thing = [
- propByName[self.shapeNamePrefix + shape.name],
- getData,
- selector,
- multiplier,
- ]
-
- @contextmanager
- def noShapeNode(self):
- """ """
- # Clear the shape node so we don't take the renaming speed hit
- if self.shapeNode:
- vectorGetter = list(
- self.shapeNode.inputPortList[0].connectedNodes.values()
- )[0]
- pointSetter = list(
- self.shapeNode.outputPortList[0].connectedNodes.values()
- )[0]
- # Also disconnect the pointSetter so we don't get the null deformation
- self.shapeTree.disconnect(2)
- self.shapeNode.delete()
- else:
- vectorGetter, pointSetter = None, None
- yield
-
- if vectorGetter is None and pointSetter is None:
- self.shapeNode = self.rebuildShapeNode(self.simplex)
- else:
- # Rebuild the shape and slider nodes
- self.shapeTree.connect(pointSetter.Execute, 2)
- shapeCompound = self.rebuildShapeNode(self.simplex)
- pointSetter.Value.connect(shapeCompound.Result)
- vectorGetter.value.connect(shapeCompound.inputPorts["In"])
- self.shapeNode = shapeCompound
- self._loadShapeIceNodes(self.simplex)
- self.rebuildSliderNode()
-
- @staticmethod
- def _getTextureProp(mesh, texName):
- """
-
- Parameters
- ----------
- mesh :
-
- texName :
-
-
- Returns
- -------
-
- """
- import pywintypes
-
- textureCls = [
- cluster
- for cluster in mesh.ActivePrimitive.Geometry.Clusters
- if cluster.Type == "sample"
- ]
- try:
- for cluster in textureCls:
- prop = cluster.Properties(texName)
- if prop:
- return prop
- except pywintypes.com_error:
- pass
- return None
-
- @classmethod
- def buildRestAbc(cls, abcMesh, name):
- """
-
- Parameters
- ----------
- abcMesh :
-
- name :
-
-
- Returns
- -------
-
- """
- with undoContext():
- meshSchema = abcMesh.getSchema()
- rawFaces = meshSchema.getFaceIndicesProperty().samples[0]
- rawCounts = meshSchema.getFaceCountsProperty().samples[0]
- rawPos = meshSchema.getPositionsProperty().samples[0]
- iuvs = meshSchema.getUVsParam()
-
- uvws = None
- if iuvs.valid():
- uvValue = iuvs.getValueProperty().getValue()
- uvs = list(zip(uvValue.x, uvValue.y, repeat(0.0)))
- if iuvs.isIndexed():
- idxs = list(iuvs.getIndexProperty().getValue())
- uvs = [uvs[i] for i in idxs]
- uvws = []
- ptr = 0
- for i in rawCounts:
- uvws.extend(reversed(uvs[ptr : ptr + i]))
- ptr += i
-
- faces = []
- ptr = 0
- for i in rawCounts:
- faces.append(i)
- faces.extend(reversed(rawFaces[ptr : ptr + i]))
- ptr += i
-
- vertexArray = [list(rawPos.x), list(rawPos.y), list(rawPos.z)]
-
- cName = "{0}_SIMPLEX".format(name)
- model = dcc.xsi.ActiveSceneRoot.AddModel(
- None, "{0}_SIMPLEXModel".format(name)
- )
- mesh = model.AddPolygonMesh(vertexArray, faces, cName)
-
- if uvws is not None:
- dcc.xsi.CreateProjection(mesh, "", "", "", cls.texName, True, "", "")
- texProp = cls._getTextureProp(mesh, cls.texName)
-
- if texProp is not None:
- dcc.xsi.FreezeObj(texProp)
- if len(uvws) == texProp.Elements.Count:
- texProp.Elements.Array = list(zip(*uvws))
- dcc.xsi.FreezeObj(texProp)
-
- return mesh
-
- @staticmethod
- def vertCount(mesh):
- geo = mesh.ActivePrimitive.Geometry
- vertArray, faceArray = geo.Get2()
- return len(vertArray[0])
-
- @undoable
- def loadAbc(self, abcMesh, js, pBar=False):
- """
-
- Parameters
- ----------
- abcMesh :
-
- js :
-
- pBar :
- (Default value = False)
-
- Returns
- -------
-
- """
- shapes = js["shapes"]
- if js["encodingVersion"] > 1:
- shapes = [i["name"] for i in shapes]
-
- if pBar is not None:
- pBar.show()
- pBar.setMaximum(len(shapes))
- longName = max(shapes, key=len)
- pBar.setValue(1)
- pBar.setLabelText("Loading:\n{0}".format("_" * len(longName)))
- QApplication.processEvents()
-
- # Load the alembic via geometry
- preAtc = self.mesh.model.Properties("alembic_timecontrol")
- loader = dcc.io.abcImport(
- abcMesh.getArchive().getName(), parentNode=self.mesh.parent()
- )
- postAtc = self.mesh.model.Properties("alembic_timecontrol")
- loader = loader[0]
- loader.name = "Loader"
- loader.Properties("Visibility").viewvis = False
- dcc.xsi.FreezeModeling(loader)
- dcc.xsi.DeleteObj(
- "{0}.polymsh.alembic_polymesh.Expression".format(loader.FullName)
- )
-
- tVal = "{0}.polymsh.alembic_polymesh.time".format(loader.FullName)
- dcc.xsi.SetValue(tVal, 0)
-
- # rester = dcc.xsi.Duplicate(loader, 1,
- # dcc.constants.siCurrentHistory, dcc.constants.siSharedParent,
- # dcc.constants.siNoGrouping, dcc.constants.siNoProperties,
- # dcc.constants.siNoAnimation, dcc.constants.siNoConstraints,
- # dcc.constants.siNoSelection)
-
- tempVertArray, tempFaceArray = loader.ActivePrimitive.Geometry.Get2()
- rester = loader.parent.AddPolygonMesh(tempVertArray, tempFaceArray, "Rester")
- rester.Properties("Visibility").viewvis = False
-
- matcher = self._matchDelta(loader, rester)
-
- cluster = self.shapeCluster.FullName
- # "{0}.polymsh.cls.Face_Shapes".format(self.mesh.FullName)
-
- with self.noShapeNode():
-
- for i, shapeName in enumerate(shapes):
- if pBar is not None:
- pBar.setValue(i)
- pBar.setLabelText("Loading:\n{0}".format(shapeName))
- QApplication.processEvents()
- if pBar.wasCanceled():
- return
- dcc.xsi.SetValue(tVal, i)
- dcc.xsi.ReplaceShapeKey(
- "{0}.{1}".format(cluster, self.shapeNamePrefix + shapeName)
- )
-
- matcher.delete()
- dcc.xsi.DeleteObj(loader)
- dcc.xsi.DeleteObj(rester)
- self.deleteShapeCombiner()
- if preAtc is None:
- if postAtc is not None:
- # Clear the alembic time control
- dcc.xsi.DeleteObj(postAtc)
-
- # Force a rebuild of the Icetree
- self.recreateShapeNode()
-
- if pBar is not None:
- pBar.setValue(len(shapes))
-
- def _matchDelta(self, loader, rester):
- """
-
- Parameters
- ----------
- loader :
-
- rester :
-
-
- Returns
- -------
-
- """
- xmlString = buildLoaderXML(loader, rester)
- fHandle, compoundPath = tempfile.mkstemp(".xsicompound", text=True)
- f = os.fdopen(fHandle, "w")
- f.write(xmlString)
- f.close()
- tree = dcc.ice.ICETree(
- None,
- self.mesh,
- "SimplexDeltaMatch",
- dcc.constants.siConstructionModePrimaryShape,
- )
- compound = dcc.ice.ICECompoundNode(
- dcc.xsi.AddICECompoundNode(compoundPath, tree._nativePointer)
- )
- tree.connect(compound.Value)
- os.remove(compoundPath)
- return tree
-
- def getShapeVertices(self, shape):
- """
-
- Parameters
- ----------
- shape :
-
-
- Returns
- -------
-
- """
- dcc.xsi.DeactivateAbove("%s.modelingmarker" % self.mesh.ActivePrimitive, True)
- restVerts = self.mesh.ActivePrimitive.Geometry.Points.PositionArray
- restVerts = np.array(list(zip(*restVerts)))
- shapeDeltas = np.array(shape.thing[0].Elements.Array).T
- dcc.xsi.DeactivateAbove("%s.modelingmarker" % self.mesh.ActivePrimitive, "")
- return restVerts + shapeDeltas
-
- def getAllShapeVertices(self, shapes, pBar=None):
- """
-
- Parameters
- ----------
- shapes :
-
- pBar :
- (Default value = None)
-
- Returns
- -------
-
- """
- for shape in shapes:
- shape.verts = self.getShapeVertices(shape)
-
- def pushAllShapeVertices(self, shapes, pBar=None):
- """
-
- Parameters
- ----------
- shapes :
-
- pBar :
- (Default value = None)
-
- Returns
- -------
-
- """
- # take all the verts stored on the shapes
- # and push them back to the DCC
- for shape in shapes:
- self.pushShapeVertices(shape)
-
- def pushShapeVertices(self, shape):
- """
-
- Parameters
- ----------
- shape :
-
-
- Returns
- -------
-
- """
- # Push the vertices for a specific shape back to the DCC
- pass
-
- def _getMeshVertices(self, mesh, world=False):
- """
-
- Parameters
- ----------
- mesh :
-
- world :
- (Default value = False)
-
- Returns
- -------
-
- """
- # We're ignoring world in XSI because we don't use it for that
- vts = mesh.ActivePrimitive.Geometry.Points.PositionArray
- vts = list(zip(*vts))
- return vts
-
- def _exportAbcVertices(self, mesh, shape, world=False):
- """
-
- Parameters
- ----------
- mesh :
-
- shape :
-
- world :
- (Default value = False)
-
- Returns
- -------
-
- """
- vts = np.array(self._getMeshVertices(mesh, world=world))
- shapeVts = np.array(shape.thing[0].Elements.Array)
- outVerts = vts + shapeVts.T
-
- vertices = V3fArray(len(vts))
- setter = V3f(0, 0, 0)
- for i in range(len(outVerts)):
- setter.setValue(outVerts[i, 0], outVerts[i, 1], outVerts[i, 2])
- vertices[i] = setter
- return vertices
-
- def _exportAbcFaces(self, mesh):
- """
-
- Parameters
- ----------
- mesh :
-
-
- Returns
- -------
-
- """
- geo = mesh.ActivePrimitive.Geometry
- vertArray, faceArray = geo.Get2()
-
- texProp = self._getTextureProp(mesh, self.texName)
- uvSample = None
- if texProp is not None:
- uvs = texProp.Elements.Array
- uvs = list(zip(*uvs))
- uvD = {uv: i for i, uv in enumerate(set(uvs))}
- uvIdxs = [uvD[uv] for uv in uvs]
- uvs = [None] * len(uvD)
- for uv, idx in six.iteritems(uvD):
- uvs[idx] = uv
- uvSample = mkUvSample(uvs, uvIdxs)
-
- ptr = 0
- faces = []
- faceCounts = []
- while ptr < len(faceArray):
- count = faceArray[ptr]
- faceCounts.append(count)
- ptr += 1
- indices = reversed(faceArray[ptr : ptr + count])
- ptr += count
- faces.extend(indices)
-
- abcFaceIndices = IntArray(len(faces))
- for i in range(len(faces)):
- abcFaceIndices[i] = faces[i]
-
- abcFaceCounts = IntArray(len(faceCounts))
- for i in range(len(faceCounts)):
- abcFaceCounts[i] = faceCounts[i]
-
- return abcFaceIndices, abcFaceCounts, uvSample
-
- @classmethod
- def getMeshTopology(cls, mesh, uvName=None):
- """Get the topology of a mesh
-
- Parameters
- ----------
- mesh : object
- The DCC Mesh to read
- uvName : str, optional
- The name of the uv set to read
-
- Returns
- -------
- np.array :
- The vertex array
- np.array :
- The "faces" array
- np.array :
- The "counts" array
- np.array :
- The uv positions
- np.array :
- The "uvFaces" array
- """
- geo = mesh.ActivePrimitive.Geometry
- vertArray, faceArray = geo.Get2()
-
- verts = list(zip(*vertArray))
- texProp = cls._getTextureProp(mesh, uvName)
- uvs, uvIdxs = None, None
- if texProp is not None:
- uvs = texProp.Elements.Array
- uvs = list(zip(*uvs))
- uvD = {uv: i for i, uv in enumerate(set(uvs))}
- uvIdxs = [uvD[uv] for uv in uvs]
- uvs = [None] * len(uvD)
- for uv, idx in six.iteritems(uvD):
- uvs[idx] = uv
-
- ptr = 0
- faces = []
- faceCounts = []
- while ptr < len(faceArray):
- count = faceArray[ptr]
- faceCounts.append(count)
- ptr += 1
- indices = reversed(faceArray[ptr : ptr + count])
- ptr += count
- faces.extend(indices)
-
- return verts, faces, faceCounts, uvs, uvIdxs
-
- def loadMeshTopology(self):
- """ """
- self._faces, self._counts, self._uvs = self._exportAbcFaces(self.mesh)
-
- def exportAbc(
- self, dccMesh, abcMesh, js, world=False, ensureCorrect=False, pBar=None
- ):
- """ """
- # dccMesh doesn't work in XSI, so just ignore it
- # export the data to alembic
- shapeDict = {i.name: i for i in self.simplex.shapes}
- if js["encodingVersion"] > 1:
- shapeNames = [i["name"] for i in js["shapes"]]
- else:
- shapeNames = js["shapes"]
- shapes = [shapeDict[i] for i in shapeNames]
-
- faces, counts, uvSample = self._exportAbcFaces(dccMesh)
- schema = abcMesh.getSchema()
-
- if pBar is not None:
- pBar.show()
- pBar.setMaximum(len(shapes))
-
- # deactivate evaluation above modeling to insure no deformations are present
- dcc.xsi.DeactivateAbove("%s.modelingmarker" % dccMesh.ActivePrimitive, True)
-
- for i, shape in enumerate(shapes):
- if pBar is not None:
- pBar.setLabelText("Exporting:\n{0}".format(shape.name))
- pBar.setValue(i)
- QApplication.processEvents()
- if pBar.wasCanceled():
- return
- verts = self._exportAbcVertices(dccMesh, shape, world)
- if uvSample is None:
- abcSample = OPolyMeshSchemaSample(verts, faces, counts)
- else:
- # I Can't just do iUVs=None. Alembic uses something different for its defaults
- abcSample = OPolyMeshSchemaSample(verts, faces, counts, iUVs=uvSample)
- schema.set(abcSample)
-
- dcc.xsi.DeactivateAbove("%s.modelingmarker" % dccMesh.ActivePrimitive, "")
-
- # Revision tracking
- def getRevision(self):
- """ """
- try:
- return self.op.inputPorts["Revision"].parameters["Revision"].value
- except AttributeError:
- return None # object does not exist
-
- def incrementRevision(self):
- """ """
- value = self.getRevision()
- if value is None:
- return
-
- dcc.xsi.SetValue("%s.Revision" % self.op.fullName, value + 1)
- d = self.simplex.buildDefinition()
- jsString = json.dumps(d)
- self.setSimplexString(self.op, jsString)
- return value + 1
-
- def setRevision(self, val):
- """
-
- Parameters
- ----------
- val :
-
-
- Returns
- -------
-
- """
- dcc.xsi.SetValue("%s.Revision" % self.op.fullName, val)
-
- # System level
- @undoable
- def renameSystem(self, name):
- """
-
- Parameters
- ----------
- name :
-
-
- Returns
- -------
-
- """
- self.name = name
- self.inProp.Name = "%s_inProperty" % name
- self.shapeTree._nativePointer.Name = "%s_IceTree" % name
- self.shapeCluster.Name = "%s_Shapes" % name
-
- # rename data in the simplex vector setter and getter
- vectorSetter = list(self.op.outputPortList[0].connectedNodes.values())[0]
- vectorGetter = list(self.shapeNode.inputPortList[0].connectedNodes.values())[0]
- # pointSetter = self.shapeNode.outputPortList[0].connectedNodes.values()[0]
- for node in [vectorSetter, vectorGetter]:
- dcc.xsi.SetValue(
- "%s.Reference" % node.fullName, "self._%s_SimplexVector" % name
- )
-
- # rename the shape nodes in the IceTree by recreating the shape node
- self.recreateShapeNode()
-
- def recreateShapeNode(self):
- """ """
- # vectorSetter = self.op.outputPortList[0].connectedNodes.values()[0]
- vectorGetter = list(self.shapeNode.inputPortList[0].connectedNodes.values())[0]
- pointSetter = list(self.shapeNode.outputPortList[0].connectedNodes.values())[0]
-
- self.shapeNode.delete()
- shapeCompound = self.rebuildShapeNode(self.simplex)
- pointSetter.Value.connect(shapeCompound.Result)
- vectorGetter.value.connect(shapeCompound.inputPorts["In"])
-
- self.shapeNode = shapeCompound
- self._loadShapeIceNodes(self.simplex)
-
- self.rebuildSliderNode()
-
- def rebuildSliderNode(self):
- """ """
- if len(self.inProp.Parameters) == 0:
- print("No input sliders")
- return
-
- if self.sliderNode:
- self.sliderNode.delete()
-
- sliderNames = [slider.name for slider in self.simplex.sliders]
- # Possibly None
- xmlString = buildSliderIceXML(sliderNames, self.name)
-
- fHandle, compoundPath = tempfile.mkstemp(".xsicompound", text=True)
- f = os.fdopen(fHandle, "w")
- f.write(xmlString)
- f.close()
- compound = dcc.ice.ICECompoundNode(
- dcc.xsi.AddICECompoundNode(compoundPath, self.shapeTree._nativePointer)
- )
- self.sliderNode = compound
- os.remove(compoundPath)
-
- self.op.inputPorts["Sliders"].connect(compound.outputPorts["Array"])
-
- # set the simplex string on simplex node
- self.setSimplexString(self.op, self.simplex.dump())
-
- def resetShapeIndexes(self):
- """ """
- # set the simplex string on simplex node
- self.setSimplexString(self.op, self.simplex.dump())
-
- # check the shape node for proper indexes
- for i, shape in enumerate(self.simplex.shapes):
- if shape.thing[2] is None:
- continue
- port = shape.thing[2].InputPorts("index")
- if port:
- if port.Value != i:
- dcc.xsi.SetValue("%s.index" % shape.thing[2], i)
- else:
- print("SHAPE HAS STRANGENESS", shape.name)
- raise RuntimeError("BAD")
-
- # Shapes
- @undoable
- def createShape(
- self,
- shape,
- live=False,
- dataReferences=None,
- deleteCombiner=True,
- rebuild=False,
- freeze=True,
- ):
- """
-
- Parameters
- ----------
- shape :
-
- live :
- (Default value = False)
- dataReferences :
- (Default value = None)
- deleteCombiner :
- (Default value = True)
- rebuild :
- (Default value = False)
- freeze :
- (Default value = True)
-
- Returns
- -------
-
- """
-
- ret, nn = self.createRawShape(
- shape.name,
- dataReferences=dataReferences,
- deleteCombiner=False,
- rebuild=rebuild,
- freeze=freeze,
- )
-
- if deleteCombiner:
- self.deleteShapeCombiner()
-
- if live:
- self.extractShape(shape, live=True, offset=10.0)
-
- shape.name = nn
- return ret
-
- def createRawShape(
- self,
- shapeName,
- dataReferences=None,
- elementArray=None,
- deleteCombiner=True,
- rebuild=True,
- freeze=True,
- ):
- """
-
- Parameters
- ----------
- shapeName :
-
- dataReferences :
- (Default value = None)
- elementArray :
- (Default value = None)
- deleteCombiner :
- (Default value = True)
- rebuild :
- (Default value = True)
- freeze :
- (Default value = True)
-
- Returns
- -------
-
- """
-
- newShape = self.shapeCluster.Properties(shapeName)
- if newShape is None:
- newShape = dcc.xsi.StoreShapeKey(
- self.shapeCluster,
- shapeName,
- dcc.constants.siShapeObjectReferenceMode,
- 1,
- 0,
- 0,
- dcc.constants.siShapeContentPrimaryShape,
- False,
- )
- # determine whether to do a inbetween shape or not based on the progression
- if elementArray:
- newShape.Elements.Array = elementArray
- else:
- dcc.shape.resetShapeKey(newShape)
- if freeze:
- dcc.xsi.FreezeObj(newShape)
-
- # create the node structure in the ICETree if not there already
- shapeNodes = self.getShapeIceNodes(newShape, dataReferences)
-
- if rebuild:
- self.resetShapeIndexes()
-
- if deleteCombiner:
- self.deleteShapeCombiner()
-
- return [newShape] + shapeNodes, newShape.Name
-
- def getInbetweenArray(self, shapes):
- """
-
- Parameters
- ----------
- shapes :
-
-
- Returns
- -------
-
- """
- blendArray = [0] * 3 * self.mesh.ActivePrimitive.Geometry.Points.Count
- shapeArray = self.getSimplexEvaluation()
- if not shapeArray:
- return None
- shapeList = [shape.name for shape in self.simplex.shapes]
-
- for shape in shapes:
- if not shape.thing:
- continue
-
- if len(shapeArray[0]) == 1:
- continue
- idx = shapeList.index(shape.name)
- shapeBlend = shapeArray[0][idx]
- shapeTuple = shape.thing[0].Elements.Array
-
- for i in range(len(shapeTuple[0])):
- blendArray[i * 3 + 0] += shapeTuple[0][i] * shapeBlend
- blendArray[i * 3 + 1] += shapeTuple[1][i] * shapeBlend
- blendArray[i * 3 + 2] += shapeTuple[2][i] * shapeBlend
-
- return blendArray
-
- @undoable
- def getShapeIceNodes(self, shapeKey, dataReferences):
- """build/return the ICE nodes related to the given shapeKey
-
- Parameters
- ----------
- shapeKey :
-
- dataReferences :
-
-
- Returns
- -------
-
- """
- simpNode = self.shapeNode
- if not dataReferences:
- dataReferences = DCC.getDataReferences(simpNode)
- ports = simpNode.exposedPorts
- if ports[0][0].isOutput():
- addNode = ports[0][0].parent
- passNode = ports[1][0].parent
- else:
- addNode = ports[1][0].parent
- passNode = ports[0][0].parent
-
- shapeRef = "%s.positions" % shapeKey.FullName.replace(
- "%s.polymsh" % self.mesh.FullName, "self"
- )
- if shapeRef in list(dataReferences.keys()):
- shapeNode = dataReferences[shapeRef]
- else:
- shapeNode = simpNode.addGetDataNode(shapeRef)
-
- multNode = shapeNode.outputPorts["value"].connectedNodes
- if "Multiply by Scalar" in list(multNode.keys()):
- multNode = multNode["Multiply by Scalar"]
- else:
- multNode = simpNode.addNode("MultiplyByScalar")
-
- if multNode.name not in list(
- shapeNode.outputPorts["value"].connectedNodes.keys()
- ):
- shapeNode.outputPorts["value"].connect(multNode.inputPorts["value"])
-
- if addNode not in list(multNode.outputPorts["result"].connectedNodes.values()):
- addPort = DCC.firstAvailablePort(addNode)
- multNode.outputPorts["result"].connect(addPort)
-
- indexNode = multNode.inputPorts["factor"].connectedNodes
- if "Select in Array" in list(indexNode.keys()):
- indexNode = indexNode["Select in Array"]
- else:
- indexNode = simpNode.addNode("SelectInArray")
- indexNode.value.connect(multNode.inputPorts["factor"])
- indexNode.inputPorts["array"].connect(passNode.outputPorts["out"])
-
- return [
- shapeNode._nativePointer,
- indexNode._nativePointer,
- multNode._nativePointer,
- ]
-
- def checkShapeValidity(self, shape, dataReferences=None):
- """
-
- Parameters
- ----------
- shape :
-
- dataReferences :
- (Default value = None)
-
- Returns
- -------
-
- """
- s = self.shapeCluster.Properties(shape.name)
- if not s:
- return False
-
- simpNode = self.shapeNode
- if not dataReferences:
- dataReferences = DCC.getDataReferences(simpNode)
- shapeRef = "%s.positions" % s.FullName.replace(
- "%s.polymsh" % self.mesh.FullName, "self"
- )
- if shapeRef not in list(dataReferences.keys()):
- return False
- return True
-
- def rebuildShapeNode(self, simp, shapeNames=None):
- """
-
- Parameters
- ----------
- simp :
-
- shapeNames :
- (Default value = None)
-
- Returns
- -------
-
- """
- if shapeNames is None:
- shapeNames = [shape.name for shape in simp.shapes]
- xmlString = buildIceXML(
- shapeNames, self.name, self.shapeCluster.Name, self.shapeNamePrefix
- )
-
- fHandle, compoundPath = tempfile.mkstemp(".xsicompound", text=True)
- f = os.fdopen(fHandle, "w")
- f.write(xmlString)
- f.close()
-
- node = dcc.xsi.AddICECompoundNode(compoundPath, self.shapeTree._nativePointer)
- shapeCompound = dcc.ice.ICECompoundNode(node)
- os.remove(compoundPath)
-
- return shapeCompound
-
- @staticmethod
- def firstAvailablePort(node):
- """
-
- Parameters
- ----------
- node :
-
-
- Returns
- -------
-
- """
- openPorts = [port for port in node.inputPortList if not port.isConnected()]
- if not openPorts:
- return node.addPortAtEnd(node.portAtEnd().name)
- else:
- return openPorts[0]
-
- @undoable
- def extractShape(self, shape, live=True, offset=10.0):
- """make a mesh representing a shape. Can be live or not
-
- Parameters
- ----------
- shape :
-
- live :
- (Default value = True)
- offset :
- (Default value = 10.0)
-
- Returns
- -------
-
- """
- shapeKey = shape.thing[0]
- print("Extracting %s" % shapeKey.Name)
- shapeGeo = self.extractShapeAsGeo(shapeKey)
- shapeGeo.posx.Value = offset
- if live:
- self.connectShape(shape, shapeGeo, live, delete=False)
- return shapeGeo
-
- @undoable
- def connectShape(
- self, shape, mesh=None, live=False, delete=False, deleteCombiner=True
- ):
- """Force a shape to match a mesh
- The "connect shape" button is:
- mesh=None, delete=True
- The "match shape" button is:
- mesh=someMesh, delete=False
- There is a possibility of a "make live" button:
- live=True, delete=False
-
- Parameters
- ----------
- shape :
-
- mesh :
- (Default value = None)
- live :
- (Default value = False)
- delete :
- (Default value = False)
- deleteCombiner :
- (Default value = True)
-
- Returns
- -------
-
- """
- # print("Connected {0}".format(shape.name))
-
- if not mesh:
- mesh = DCC.findExtractedShape(shape.name)
- if not mesh:
- print("No extracted shape found to connect for %s" % shape.name)
- return
-
- # print("Connected {0}".format(shape.name))
-
- tempShape = dcc.xsi.SelectShapeKey(
- self.shapeCluster,
- mesh,
- dcc.constants.siShapeObjectReferenceMode,
- live,
- False,
- )[0]
- dcc.xsi.DeleteObj(shape.thing[0])
- tempShape.Name = shape.name
- shape.thing[0] = tempShape
-
- # print("Connected {0}".format(tempShape.Name))
-
- if not live:
- dcc.xsi.FreezeObj(tempShape)
- if delete:
- dcc.xsi.DeleteObj(mesh)
- if deleteCombiner:
- self.deleteShapeCombiner()
-
- # print("Connected {0}".format(tempShape.Name))
-
- @staticmethod
- def findExtractedShape(shape):
- """
-
- Parameters
- ----------
- shape :
-
-
- Returns
- -------
-
- """
- testName = "%s_Extract" % shape
- testObj = dcc.xsi.ActiveSceneRoot.FindChild(testName)
-
- return testObj
-
- @undoable
- def extractPosedShape(self, shape):
- """
-
- Parameters
- ----------
- shape :
-
-
- Returns
- -------
-
- """
- pass
-
- @undoable
- def zeroShape(self, shape):
- """Set the shape to be completely zeroed
-
- Parameters
- ----------
- shape :
-
-
- Returns
- -------
-
- """
- shapeKey = shape.thing[0]
- dcc.shape.resetShapeKey(shapeKey)
- dcc.xsi.FreezeObj(shapeKey)
-
- print("Shape %s has been reset" % shape.name)
-
- @undoable
- def deleteShape(self, toDelShape):
- """Remove a shape from the system
-
- Parameters
- ----------
- toDelShape :
-
-
- Returns
- -------
-
- """
- dcc.xsi.DeleteObj(toDelShape.thing)
-
- # rebuild the operator
- self.resetShapeIndexes()
-
- @undoable
- def renameShape(self, shape, name):
- """Change the name of the shape
-
- Parameters
- ----------
- shape :
-
- name :
-
-
- Returns
- -------
-
- """
- shape.thing[0].Name = name
- name = shape.thing[0].Name
-
- shapeNodeList = shape.thing[1].Reference.Value.split(".")
- shapeNodeList[-2] = name
- shape.thing[1].Reference.Value = ".".join(shapeNodeList)
-
- self.resetShapeIndexes()
-
- @undoable
- def convertShapeToCorrective(self, shape):
- """
-
- Parameters
- ----------
- shape :
-
-
- Returns
- -------
-
- """
- pass
-
- def deleteShapeCombiner(self):
- """ """
- clsCombiner = dcc.operator.getOperatorFromStack(
- self.mesh, "clustershapecombiner"
- )
- if clsCombiner:
- dcc.xsi.DeleteObj(clsCombiner)
-
- def freezeAllShapes(self):
- """ """
- toFreeze = [
- shape.thing[0]
- for shape in self.simplex.shapes
- if len(shape.thing[0].NestedObjects) > 2
- ]
- dcc.xsi.FreezeObj(toFreeze)
-
- # Falloffs
- def createFalloff(self, falloff):
- """
-
- Parameters
- ----------
- falloff :
-
-
- Returns
- -------
-
- """
- pass # for eventual live splits
-
- def duplicateFalloff(self, falloff, newFalloff, newName):
- """
-
- Parameters
- ----------
- falloff :
-
- newFalloff :
-
- newName :
-
-
- Returns
- -------
-
- """
- pass # for eventual live splits
-
- def deleteFalloff(self, falloff):
- """
-
- Parameters
- ----------
- falloff :
-
-
- Returns
- -------
-
- """
- pass # for eventual live splits
-
- def setFalloffData(
- self, falloff, splitType, axis, minVal, minHandle, maxHandle, maxVal, mapName
- ):
- """
-
- Parameters
- ----------
- falloff :
-
- splitType :
-
- axis :
-
- minVal :
-
- minHandle :
-
- maxHandle :
-
- maxVal :
-
- mapName :
-
-
- Returns
- -------
-
- """
- pass # for eventual live splits
-
- def getFalloffThing(self, falloff):
- """
-
- Parameters
- ----------
- falloff :
-
-
- Returns
- -------
-
- """
- return falloff.name
-
- # Sliders
- @undoable
- def createSlider(self, slider, rebuildOp=True):
- """Create a new slider with a name
-
- Parameters
- ----------
- slider :
-
- rebuildOp :
- (Default value = True)
-
- Returns
- -------
-
- """
- name = slider.name
- param = self.createSliderParam(slider, name)
-
- # connect solver
- if rebuildOp:
- self.rebuildSliderNode()
- self.resetShapeIndexes()
- self.deleteShapeCombiner()
- return param
-
- @undoable
- def renameSlider(self, slider, name):
- """Set the name of a slider
-
- Parameters
- ----------
- slider :
-
- name :
-
-
- Returns
- -------
-
- """
- param = self.createSliderParam(slider, name)
- if slider.thing.IsAnimated(dcc.constants.siAnySource):
- dcc.xsi.CopyAnimation(slider.thing, True, True, False, True, True)
- dcc.xsi.PasteAnimation(param, 1)
-
- dcc.xsi.RemoveCustomParam(slider.thing)
- slider.thing = param
-
- self.rebuildSliderNode()
- self.resetShapeIndexes()
-
- @undoable
- def setSliderRange(self, slider):
- """
-
- Parameters
- ----------
- slider :
-
-
- Returns
- -------
-
- """
- vals = [v.value for v in slider.prog.pairs]
- dcc.xsi.EditParameterDefinition(
- slider.thing,
- "",
- "",
- -2.0,
- 2.0,
- min(vals) * self.sliderMul,
- max(vals) * self.sliderMul,
- )
-
- @undoable
- def renameCombo(self, combo, name):
- """
-
- Parameters
- ----------
- combo :
-
- name :
-
-
- Returns
- -------
-
- """
- pass
-
- @undoable
- def deleteSlider(self, toDelSlider):
- """Remove a slider
-
- Parameters
- ----------
- toDelSlider :
-
-
- Returns
- -------
-
- """
- dcc.xsi.RemoveCustomParam(toDelSlider.thing)
- self.rebuildSliderNode()
- self.resetShapeIndexes()
-
- def createSliderParam(self, slider, name):
- """
-
- Parameters
- ----------
- slider :
-
- name :
-
-
- Returns
- -------
-
- """
- vals = [v.value for v in slider.prog.pairs]
- param = self.inProp.AddParameter3(name, dcc.constants.siFloat, 0, -2.0, 2.0)
- dcc.xsi.EditParameterDefinition(
- param,
- "",
- "",
- -2.0,
- 2.0,
- min(vals) * self.sliderMul,
- max(vals) * self.sliderMul,
- )
- return param
-
- @undoable
- def addProgFalloff(self, prog, falloff):
- """
-
- Parameters
- ----------
- prog :
-
- falloff :
-
-
- Returns
- -------
-
- """
- pass # for eventual live splits
-
- @undoable
- def removeProgFalloff(self, prog, falloff):
- """
-
- Parameters
- ----------
- prog :
-
- falloff :
-
-
- Returns
- -------
-
- """
- pass # for eventual live splits
-
- @undoable
- def setSlidersWeights(self, sliders, weights):
- """Set the weight of a slider. This does not change the definition
-
- Parameters
- ----------
- sliders :
-
- weights :
-
-
- Returns
- -------
-
- """
- for slider, weight in zip(sliders, weights):
- slider.thing.Value = weight
-
- @undoable
- def setSliderWeight(self, slider, weight):
- """
-
- Parameters
- ----------
- slider :
-
- weight :
-
-
- Returns
- -------
-
- """
- slider.thing.Value = weight
-
- @undoable
- def updateSlidersRange(self, sliders):
- """
-
- Parameters
- ----------
- sliders :
-
-
- Returns
- -------
-
- """
- for slider in sliders:
- vals = [v.value for v in slider.prog.pairs]
- dcc.xsi.EditParameterDefinition(
- slider.thing,
- "",
- "",
- -2.0,
- 2.0,
- min(vals) * self.sliderMul,
- max(vals) * self.sliderMul,
- )
-
- # Combos
- def _createDelta(self, combo, target, tVal):
- """Part of the combo extraction process.
- Combo shapes are fixit shapes added on top of any sliders.
- This means that the actual combo-shape by itself will not look good by itself,
- and that's bad for artist interaction.
- So we must create a setup to take the final sculpted shape, and subtract
- the any direct slider deformations to get the actual "combo shape" as a delta
- It is this delta shape that is then plugged into the system
-
- Parameters
- ----------
- combo :
-
- target :
-
- tVal :
-
-
- Returns
- -------
-
- """
-
- # check if delta object already exists
- deltaName = "%s_Delta" % combo.name
- deltaObj = dcc.xsi.ActiveSceneRoot.FindChild(deltaName)
- if deltaObj:
- return deltaObj
-
- # create and extract a temporary shape to use as a reference
- # of everything but the actual combo shape
- tempShape = dcc.xsi.StoreShapeKey(
- self.shapeCluster,
- "temp",
- dcc.constants.siShapeObjectReferenceMode,
- 1,
- 0,
- 0,
- dcc.constants.siShapeContentPrimaryShape,
- False,
- )
- affected = self.getComboAffected(combo)
-
- with disconnected(affected[0], self.inProp):
- # set sliders for the combo
- for pair in combo.pairs:
- pair.slider.thing.Value = pair.value * tVal
-
- tempShape.Elements.Array = self.getInbetweenArray(affected[1])
-
- # build delta object
- deltaObj = self.extractShapeAsGeo(tempShape)
- deltaObj.Name = deltaName
- deltaObj.Properties("visibility").Parameters("viewvis").Value = False
- deltaObj.posx.Value = 10.0
- deltaObj.posz.Value = -10.0
-
- # apply shape of target to delta object
- dcc.xsi.SelectShapeKey(
- deltaObj, target, dcc.constants.siShapeObjectReferenceMode, True, True
- )[0]
-
- # get undeformed shape to apply to get delta, and apply as a shape to delta
- tempBase = self.extractShapeAsGeo(self.simplex.restShape.thing[0])
-
- dcc.xsi.SelectShapeKey(
- deltaObj, tempBase, dcc.constants.siShapeObjectReferenceMode, False, True
- )[0]
-
- for param in deltaObj.Properties("ShapeWeights").Parameters:
- dcc.xsi.RemoveAnimation(param)
- param.Value = 1
-
- dcc.xsi.DeleteObj(tempBase)
- dcc.xsi.DeleteObj(tempShape)
-
- return deltaObj
-
- def getComboAffected(self, combo):
- """
-
- Parameters
- ----------
- combo :
-
-
- Returns
- -------
- type
-
-
- """
- # Get all related sliders to the combo
- comboSliders = [i.slider for i in combo.pairs]
- comboSlidersSet = set(comboSliders)
-
- # get list of combo shapes that have an impact on the current combo
- affected = []
- for slider in comboSliders:
- for pair in slider.prog.pairs:
- if pair.shape not in affected:
- affected.append(pair.shape)
- for c in self.simplex.combos:
- if c == combo:
- continue
- cSliders = set(i.slider for i in c.pairs)
- if cSliders <= comboSlidersSet:
- for pair in c.prog.pairs:
- affected.append(pair.shape)
-
- return [comboSliders, affected]
-
- @undoable
- def extractTraversalShape(self, trav, shape, live=True, offset=10.0):
- """
-
- Parameters
- ----------
- trav :
-
- shape :
-
- live :
- (Default value = True)
- offset :
- (Default value = 10.0)
-
- Returns
- -------
-
- """
- pass
-
- @undoable
- def connectTraversalShape(self, trav, shape, mesh=None, live=True, delete=False):
- """
-
- Parameters
- ----------
- trav :
-
- shape :
-
- mesh :
- (Default value = None)
- live :
- (Default value = True)
- delete :
- (Default value = False)
-
- Returns
- -------
-
- """
- pass
-
- @undoable
- def extractComboShape(self, combo, shape, live=True, offset=10.0):
- """Extract a shape from a combo progression
-
- Parameters
- ----------
- combo :
-
- shape :
-
- live :
- (Default value = True)
- offset :
- (Default value = 10.0)
-
- Returns
- -------
-
- """
-
- # extract the rest shape to use as a base
- tempShape = dcc.xsi.StoreShapeKey(
- self.shapeCluster,
- "temp",
- dcc.constants.siShapeObjectReferenceMode,
- 1,
- 0,
- 0,
- dcc.constants.siShapeContentPrimaryShape,
- False,
- )
-
- affected = self.getComboAffected(combo)
-
- with disconnected(affected[0], self.inProp):
- # set the relevant sliders for the combo
- for pair in combo.pairs:
- pair.slider.thing.Value = pair.value
-
- # also grab the shape that we are affecting
- affected[1].append(shape)
-
- # get the blendarray
- tempShape.Elements.Array = self.getInbetweenArray(affected[1])
-
- # extracted = dcc.shape.extractShapeKeyAsGeo(tempShape)
- extracted = self.extractShapeAsGeo(tempShape)
- extracted.Name = "%s_Extract" % shape.name
- extracted.posx.Value = offset
-
- dcc.xsi.DeleteObj(tempShape)
-
- if live:
- self.connectComboShape(combo, shape, extracted, live=live, delete=False)
-
- return extracted
-
- @undoable
- def connectComboShape(self, combo, shape, mesh=None, live=False, delete=False):
- """Connect a shape into a combo progression
-
- Parameters
- ----------
- combo :
-
- shape :
-
- mesh :
- (Default value = None)
- live :
- (Default value = False)
- delete :
- (Default value = False)
-
- Returns
- -------
-
- """
- if not mesh:
- mesh = DCC.findExtractedShape(shape.name)
- if not mesh:
- print("No extracted shape found to connect for %s" % shape.name)
- return
- shapeIdx = combo.prog.getShapeIndex(shape)
- tVal = combo.prog.pairs[shapeIdx].value
- delta = self._createDelta(combo, mesh, tVal)
- self.connectShape(shape, delta, live, delete)
- if delete:
- dcc.xsi.DeleteObj(mesh)
-
- @undoable
- def extractShapeAsGeo(self, shapeKey):
- """
-
- Parameters
- ----------
- shapeKey :
-
-
- Returns
- -------
-
- """
- # create new object with same shape as mesh using dirty tricks to set geometry directly
- temp = dcc.xsi.ActiveSceneRoot.AddGeometry(
- "Cube", "MeshSurface", "%s_Extract" % shapeKey.Name
- )
- dcc.xsi.FreezeObj(temp)
- dcc.xsi.DeactivateAbove("%s.modelingmarker" % self.mesh.ActivePrimitive, True)
- vertArray, faceArray = self.mesh.ActivePrimitive.Geometry.Get2()
- temp.ActivePrimitive.Geometry.set(vertArray, faceArray)
- dcc.xsi.DeactivateAbove("%s.modelingmarker" % self.mesh.ActivePrimitive, "")
-
- # copy over the shape
- tempShape = dcc.xsi.StoreShapeKey(
- temp, "Shape", dcc.constants.siShapeObjectReferenceMode
- )
- tempShape.Elements.Array = shapeKey.Elements.Array
- dcc.xsi.ApplyShapeKey(tempShape, None, None, 100, None, None, None, 2)
- dcc.xsi.FreezeObj(temp)
-
- return temp
-
- @staticmethod
- def setDisabled(op):
- """
-
- Parameters
- ----------
- op :
-
-
- Returns
- -------
-
- """
- nc = op.rootNodeContainer
- pairs = []
- for pName, port in six.iteritems(nc.inputPorts):
- outs = list(port.connectedPorts.values())
- if outs:
- pairs.append((port, outs[0]))
- port.disconnect()
- return pairs
-
- @staticmethod
- def reEnable(helpers):
- """
-
- Parameters
- ----------
- helpers :
-
-
- Returns
- -------
-
- """
- for inPort, outPort in helpers:
- inPort.connect(outPort)
-
- # Data Access
- @staticmethod
- def getSimplexOperatorsOnObject(thing):
- """
-
- Parameters
- ----------
- thing :
-
-
- Returns
- -------
- type
-
-
- """
- out = []
- for tree in thing.ActivePrimitive.ICETrees:
- simplexNodes = [
- dcc.ice.ICENode(node)
- for node in tree.Nodes
- if node.Type == "SimplexNode"
- ]
- out.extend(simplexNodes)
- return out
-
- @staticmethod
- def getSimplexOperatorsOnObjectByName(thing, name):
- """
-
- Parameters
- ----------
- thing :
-
- name :
-
-
- Returns
- -------
- type
-
-
- """
- ops = DCC.getSimplexOperatorsOnObject(thing)
- ops = DCC.filterSimplexByName(ops, name)
-
- if not ops:
- return None
- else:
- return ops[0]
-
- @staticmethod
- def getSimplexString(op):
- """
-
- Parameters
- ----------
- op :
-
-
- Returns
- -------
- type
-
-
- """
- return op.inputPorts["Definition"].parameters["Definition_string"].value
-
- @staticmethod
- def getSimplexStringOnThing(thing, systemName):
- """
-
- Parameters
- ----------
- thing :
-
- systemName :
-
-
- Returns
- -------
- type
-
-
- """
- op = DCC.getSimplexOperatorsOnObjectByName(thing, systemName)
- return DCC.getSimplexString(op)
-
- @staticmethod
- def filterSimplexByObject(ops, thing):
- """
-
- Parameters
- ----------
- ops :
-
- thing :
-
-
- Returns
- -------
-
- """
- filtered = []
- if not ops:
- return filtered
- for i in ops:
- if not i.Parent3DObject:
- # dcc.xsi.DeleteObj(i)
- continue
- if i.Parent3DObject.IsEqualTo(thing):
- filtered.append(i)
- return filtered
-
- @staticmethod
- def filterSimplexByName(ops, name):
- """
-
- Parameters
- ----------
- ops :
-
- name :
-
-
- Returns
- -------
-
- """
- filtered = []
- if not ops:
- return filtered
- for i in ops:
- js = json.loads(DCC.getSimplexString(i))
- if js["systemName"] == name:
- filtered.append(i)
- return filtered
-
- @staticmethod
- def setSimplexString(op, val):
- """set the definition string from a simplex operator
-
- Parameters
- ----------
- op :
-
- val :
-
-
- Returns
- -------
-
- """
- if op:
- dcc.xsi.SetValue("%s.Definition_string" % op.fullName, val)
- return val
- else:
- return val
-
- @staticmethod
- def selectObject(thing):
- """Select an object in the DCC
-
- Parameters
- ----------
- thing :
-
-
- Returns
- -------
-
- """
- dcc.xsi.SelectObj(thing)
-
- def selectCtrl(self):
- """Select the system's control object"""
- self.selectObject(self.inProp)
-
- @staticmethod
- def getObjectByName(name):
- """
-
- Parameters
- ----------
- name :
-
-
- Returns
- -------
- type
-
-
- """
- return dcc.xsi.Dictionary.GetObject(name, False)
-
- @staticmethod
- def getObjectName(thing):
- """
-
- Parameters
- ----------
- thing :
-
-
- Returns
- -------
- type
-
-
- """
- return thing.Name
-
- @staticmethod
- def getSelectedObjects():
- """ """
- # For maya, only return transform nodes
- return list(dcc.xsi.Selection)
-
- @staticmethod
- def getDataReferences(node):
- """
-
- Parameters
- ----------
- node :
-
-
- Returns
- -------
-
- """
- dataDict = {}
- for n in node.nodes:
- if n.type != "SceneReferenceNode":
- continue
- dataDict[n.Reference.Value] = n
- return dataDict
-
- @staticmethod
- def staticUndoOpen():
- """ """
- dcc.xsi.OpenUndo("SimplexUndo")
-
- @staticmethod
- def staticUndoClose():
- """ """
- dcc.xsi.CloseUndo()
-
- def undoOpen(self):
- """ """
- if self.undoDepth == 0:
- self.staticUndoOpen()
- self.undoDepth += 1
-
- def undoClose(self):
- """ """
- self.undoDepth -= 1
- if self.undoDepth == 0:
- self.staticUndoClose()
-
- def getSimplexEvaluation(self):
- """ """
- geo = self.mesh.ActivePrimitive.Geometry
- evalArray = geo.GetICEAttributeFromName(
- "_%s_SimplexVector" % self.name
- ).DataArray2D
- return evalArray
-
- @classmethod
- def getPersistentFalloff(cls, thing):
- """
-
- Parameters
- ----------
- thing :
-
-
- Returns
- -------
-
- """
- return thing.FullName
-
- @classmethod
- def loadPersistentFalloff(cls, thing):
- """
-
- Parameters
- ----------
- thing :
-
-
- Returns
- -------
-
- """
- return cls.getObjectByName(thing)
-
- @classmethod
- def getPersistentShape(cls, thing):
- """
-
- Parameters
- ----------
- thing :
-
-
- Returns
- -------
-
- """
- # return cls.getObjectName(thing)
- if thing is None:
- return None
- names = []
- for item in thing:
- app = None if item is None else item.FullName
- names.append(app)
- return names
-
- @classmethod
- def loadPersistentShape(cls, thing):
- """
-
- Parameters
- ----------
- thing :
-
-
- Returns
- -------
-
- """
- if thing is None:
- return None
- items = []
- for name in thing:
- items.append(cls.getObjectByName(name))
- return items
-
- @classmethod
- def getPersistentSlider(cls, thing):
- """
-
- Parameters
- ----------
- thing :
-
-
- Returns
- -------
-
- """
- return thing.FullName
-
- @classmethod
- def loadPersistentSlider(cls, thing):
- """
-
- Parameters
- ----------
- thing :
-
-
- Returns
- -------
-
- """
- return cls.getObjectByName(thing)
-
- def getFreezeThing(self, combo):
- return []
-
-
-class SliderDispatch(QtCore.QObject):
- """ """
-
- valueChanged = Signal()
-
- def __init__(self, node, parent=None):
- super(SliderDispatch, self).__init__(parent)
-
- def emitValueChanged(self, *args, **kwargs):
- """
-
- Parameters
- ----------
- *args :
-
- **kwargs :
-
-
- Returns
- -------
-
- """
- self.valueChanged.emit()
-
-
-class Dispatch(QtCore.QObject):
- """ """
-
- beforeNew = Signal()
- afterNew = Signal()
- beforeOpen = Signal()
- afterOpen = Signal()
- undo = Signal()
- redo = Signal()
-
- def __init__(self, parent=None):
- super(Dispatch, self).__init__(parent)
- self.callbackIDs = []
- self.connectCallbacks()
-
- def connectCallbacks(self):
- """ """
- if self.callbackIDs:
- self.disconnectCallbacks()
-
- # self.callbackIDs.append(om.MSceneMessage.addCallback(om.MSceneMessage.kBeforeNew, self.emitBeforeNew))
- # self.callbackIDs.append(om.MSceneMessage.addCallback(om.MSceneMessage.kAfterNew, self.emitAfterNew))
- # self.callbackIDs.append(om.MSceneMessage.addCallback(om.MSceneMessage.kBeforeOpen, self.emitBeforeOpen))
- # self.callbackIDs.append(om.MSceneMessage.addCallback(om.MSceneMessage.kAfterOpen, self.emitAfterOpen))
- # self.callbackIDs.append(om.MEventMessage.addEventCallback("Undo", self.emitUndo))
- # self.callbackIDs.append(om.MEventMessage.addEventCallback("Redo", self.emitRedo))
-
- def disconnectCallbacks(self):
- """ """
- for i in self.callbackIDs:
- # om.MMessage.removeCallback(i)
- pass
-
- def emitBeforeNew(self, *args, **kwargs):
- """
-
- Parameters
- ----------
- *args :
-
- **kwargs :
-
-
- Returns
- -------
-
- """
- self.beforeNew.emit()
-
- def emitAfterNew(self, *args, **kwargs):
- """
-
- Parameters
- ----------
- *args :
-
- **kwargs :
-
-
- Returns
- -------
-
- """
- self.afterNew.emit()
-
- def emitBeforeOpen(self, *args, **kwargs):
- """
-
- Parameters
- ----------
- *args :
-
- **kwargs :
-
-
- Returns
- -------
-
- """
- self.beforeOpen.emit()
-
- def emitAfterOpen(self, *args, **kwargs):
- """
-
- Parameters
- ----------
- *args :
-
- **kwargs :
-
-
- Returns
- -------
-
- """
- self.afterOpen.emit()
-
- def emitUndo(self, *args, **kwargs):
- """
-
- Parameters
- ----------
- *args :
-
- **kwargs :
-
-
- Returns
- -------
-
- """
- self.undo.emit()
-
- def emitRedo(self, *args, **kwargs):
- """
-
- Parameters
- ----------
- *args :
-
- **kwargs :
-
-
- Returns
- -------
-
- """
- self.redo.emit()
-
- def __del__(self):
- self.disconnectCallbacks()
-
-
-DISPATCH = Dispatch()
-
-
-def rootWindow():
- """Returns the currently active QT main window
- Only works for QT UI's like Maya
-
- Parameters
- ----------
-
- Returns
- -------
-
- """
- # for MFC apps there should be no root window
- window = None
- if QApplication.instance():
- inst = QApplication.instance()
- window = inst.activeWindow()
- # Ignore QSplashScreen's, they should never be considered the root window.
- if isinstance(window, QSplashScreen):
- return None
- # If the application does not have focus try to find A top level widget
- # that doesn't have a parent and is a QMainWindow or QDialog
- if window is None:
- windows = []
- dialogs = []
- for w in QApplication.instance().topLevelWidgets():
- if w.parent() is None:
- if isinstance(w, QMainWindow):
- windows.append(w)
- elif isinstance(w, QDialog):
- dialogs.append(w)
- if windows:
- window = windows[0]
- elif dialogs:
- window = dialogs[0]
-
- # grab the root window
- if window:
- while True:
- parent = window.parent()
- if not parent:
- break
- if isinstance(parent, QSplashScreen):
- break
- window = parent
-
- return window
diff --git a/scripts/simplexui/interfaceModel_Test.py b/scripts/simplexui/interfaceModel_Test.py
deleted file mode 100644
index c8f0ad92..00000000
--- a/scripts/simplexui/interfaceModel_Test.py
+++ /dev/null
@@ -1,259 +0,0 @@
-# Copyright 2016, Blur Studio
-#
-# This file is part of Simplex.
-#
-# Simplex is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Simplex is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with Simplex. If not, see .
-
-from __future__ import absolute_import, print_function
-
-import os
-import sys
-
-from six.moves import range
-
-# Add the parent folder to the path so I can import SimplexUI
-# Means I can run this test code from inside the module and
-# keep everything together
-base = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-sys.path.insert(0, base)
-
-from .interfaceModel import (
- ComboFilterModel,
- ComboModel,
- Simplex,
- SimplexModel,
- Slider,
- SliderFilterModel,
- SliderModel,
- TraversalFilterModel,
- TraversalModel,
-)
-from Qt.QtCore import QModelIndex
-from Qt.QtWidgets import QApplication, QPushButton, QTreeView, QVBoxLayout, QWidget
-
-
-# HELPERS
-def expandRecursive(view, model, index=QModelIndex(), depth=0, debug=False):
- """helper to expand the whole tree"""
- view.setExpanded(index, True)
- rows = model.rowCount(index)
-
- if debug:
- item = model.itemFromIndex(index)
- print(
- " " * depth
- + "Name {0}, children {1}".format(item.name if item else None, rows)
- )
- print(
- " " * depth
- + "Depth {0}, Row {1}, Col {2}".format(depth, index.row(), index.column())
- )
-
- for row in range(rows):
- child = model.index(row, 0, index)
- if not child.isValid():
- continue
- expandRecursive(view, model, child, depth + 1)
-
-
-def showTree(model):
- app = QApplication(sys.argv)
- tv = QTreeView()
- tv.setModel(model)
- expandRecursive(tv, model)
- tv.resizeColumnToContents(0)
- tv.show()
- sys.exit(app.exec_())
-
-
-def buildDummySystem(path, name="Face"):
- if path.endswith(".json"):
- simp = Simplex.buildSystemFromFile(path)
- elif path.endswith(".smpx"):
- simp = Simplex.buildSystemFromFile(path)
- else:
- raise IOError("Filepath is not .smpx or .json")
- return simp
-
-
-# DISPLAY TESTS
-def testSliderDisplay(smpxPath, applyFilter=True):
- simp = Simplex.buildSystemFromFile(smpxPath)
- model = SimplexModel(simp, None)
- model = SliderModel(model)
- if applyFilter:
- model = SliderFilterModel(model)
- showTree(model)
-
-
-def testComboDisplay(smpxPath, applyFilter=True):
- simp = Simplex.buildSystemFromFile(smpxPath)
- model = SimplexModel(simp, None)
- model = ComboModel(model)
- if applyFilter:
- model = ComboFilterModel(model)
- showTree(model)
-
-
-def testTraversalDisplay(path, applyFilter=True):
- simp = buildDummySystem(path)
-
- model = SimplexModel(simp, None)
- model = TraversalModel(model)
- if applyFilter:
- model = TraversalFilterModel(model)
- showTree(model)
-
-
-def testBaseDisplay(path):
- simp = buildDummySystem(path)
-
- model = SimplexModel(simp, None)
- showTree(model)
-
-
-def testEmptySimplex():
- simp = Simplex.buildEmptySystem(None, "Face")
- model = SimplexModel(simp, None)
- model = SliderModel(model)
- showTree(model)
-
-
-# RowAdd Tests
-def testNewSlider():
- simp = Simplex.buildEmptySystem(None, "Face")
- model = SimplexModel(simp, None)
- smodel = SliderModel(model)
- fmodel = SliderFilterModel(smodel)
- fmodel.doFilter = True
-
- app = QApplication(sys.argv)
-
- topWid = QWidget()
- lay = QVBoxLayout(topWid)
-
- tv = QTreeView(topWid)
- btn = QPushButton("NEW", topWid)
- lay.addWidget(tv)
- lay.addWidget(btn)
-
- tv.setModel(fmodel)
- expandRecursive(tv, fmodel)
- topWid.show()
-
- def newSlider():
- return Slider.createSlider("NewSlider", simp)
-
- btn.clicked.connect(newSlider)
-
- sys.exit(app.exec_())
-
-
-def testDeleteBase(path):
- simp = buildDummySystem(path)
- model = SimplexModel(simp, None)
-
- # model = SliderModel(model)
- # model = SliderFilterModel(model)
-
- model = ComboModel(model)
- # model = ComboFilterModel(model)
-
- app = QApplication(sys.argv)
-
- topWid = QWidget()
- lay = QVBoxLayout(topWid)
-
- tv = QTreeView(topWid)
-
- btn = QPushButton("DELETE", topWid)
- lay.addWidget(tv)
- lay.addWidget(btn)
-
- tv.setModel(model)
- topWid.show()
-
- expandRecursive(tv, model)
- tv.resizeColumnToContents(0)
-
- def delCallback():
- sel = tv.selectedIndexes()
- sel = [i for i in sel if i.column() == 0]
- items = [s.model().itemFromIndex(s) for s in sel]
- item = items[0]
- print("Deleting", type(item), item.name)
- item.delete()
- tv.model().invalidateFilter()
-
- btn.clicked.connect(delCallback)
-
- sys.exit(app.exec_())
-
-
-def testNewChild(path):
- simp = buildDummySystem(path)
- model = SimplexModel(simp, None)
- model = SliderModel(model)
- model = SliderFilterModel(model)
-
- app = QApplication(sys.argv)
-
- topWid = QWidget()
- lay = QVBoxLayout(topWid)
-
- tv = QTreeView(topWid)
-
- btn = QPushButton("NEW", topWid)
- lay.addWidget(tv)
- lay.addWidget(btn)
-
- tv.setModel(model)
- topWid.show()
-
- expandRecursive(tv, model)
- tv.resizeColumnToContents(0)
-
- def newCallback():
- sel = tv.selectedIndexes()
- sel = [i for i in sel if i.column() == 0]
- items = [s.model().itemFromIndex(s) for s in sel]
- # item = items[0]
-
- # TODO
- # find the child type of item
- # make a new one of those
-
- # tv.model().invalidateFilter()
-
- btn.clicked.connect(newCallback)
-
- sys.exit(app.exec_())
-
-
-if __name__ == "__main__":
- # basePath = r'D:\Users\tyler\Documents\GitHub\Simplex\scripts\SimplexUI\build'
- basePath = r"D:\Users\tyler\Documents\GitHub\Simplex\Useful"
- # path = os.path.join(basePath, 'male_Simplex_v005_Split.smpx')
- # path = os.path.join(basePath, 'sphere_abcd_50.smpx')
- # path = os.path.join(basePath, 'male_traversal3.json')
- path = os.path.join(basePath, "SquareTest_Floater.json")
-
- # Only works for one at a time
- # testEmptySimplex()
- # testBaseDisplay(path)
- # testSliderDisplay(path, applyFilter=True)
- # testComboDisplay(path, applyFilter=True)
- # testTraversalDisplay(path, applyFilter=True)
- # testNewSlider()
- testDeleteBase(path)
diff --git a/scripts/simplexui/menu/xsiPlugins/__init__.py b/scripts/simplexui/menu/xsiPlugins/__init__.py
deleted file mode 100644
index c90769bd..00000000
--- a/scripts/simplexui/menu/xsiPlugins/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2016, Blur Studio
-#
-# This file is part of Simplex.
-#
-# Simplex is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Simplex is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with Simplex. If not, see .
diff --git a/simplex_maya_installer.py b/simplex_maya_installer.py
index 782250ca..4b1f3065 100644
--- a/simplex_maya_installer.py
+++ b/simplex_maya_installer.py
@@ -1,4 +1,5 @@
import logging
+import importlib.util
import os
import sys
import zipfile
@@ -124,10 +125,10 @@ def get_latest_git_release(user, repo, asset_regex, out_path):
out_path = Path(out_path)
outFolder = out_path.parent
outFolder.mkdir(exist_ok=True)
- logger.info(f"Downloading latest simplex")
+ logger.info("Downloading latest simplex")
logger.info(f"from: {download_url}")
logger.info(f"to: {out_path}")
- path, headers = urllib.request.urlretrieve(download_url, filename=out_path)
+ path, _headers = urllib.request.urlretrieve(download_url, filename=out_path)
return Path(path)
@@ -171,14 +172,14 @@ def get_numpy_simplex_target(mod_folder):
return nppath
-def onMayaDroppedPythonFile(obj):
+def onMayaDroppedPythonFile(_obj):
"""This function will get run when you drag/drop this python script onto maya"""
try:
# Ensure that people will report a full error
cmds.optionVar(intValue=("stackTraceIsOn", 1))
mel.eval('synchronizeScriptEditorOption(1, "StackTraceMenuItem")')
- mod_folder = Path(cmds.internalVar(uad=True)) / "modules"
+ mod_folder = Path(cmds.internalVar(userAppDir=True)) / "modules"
modfile = mod_folder / "simplex.mod"
simplex_zip = mod_folder / "simplex.zip"
moddir = mod_folder / "simplex"
@@ -211,22 +212,20 @@ def onMayaDroppedPythonFile(obj):
raise RuntimeError("Download of simplex zip failed")
with zipfile.ZipFile(simplex_zip, "r") as zip_ref:
- zip_ref.extractall(mod_folder)
+ members = [m for m in zip_ref.namelist() if m.startswith("modules/")]
+ zip_ref.extractall(mod_folder.parent, members=members)
+
os.remove(simplex_zip)
mayapy = get_mayapy_path()
target = get_numpy_simplex_target(mod_folder)
- try:
- import numpy
- except ModuleNotFoundError:
+ if importlib.util.find_spec("numpy") is None:
install_numpy(mayapy, target)
else:
logger.info("Numpy is already installed for this version of maya")
- try:
- import Qt
- except ModuleNotFoundError:
+ if importlib.util.find_spec("Qt") is None:
install_qtpy(mayapy, target)
else:
logger.info("Qt.py is already installed for this version of maya")
@@ -239,4 +238,3 @@ def onMayaDroppedPythonFile(obj):
message="Simplex installation complete",
button=["OK"],
)
-
diff --git a/src/maya/meson.build b/src/maya/meson.build
index 54c308f3..36fac10b 100644
--- a/src/maya/meson.build
+++ b/src/maya/meson.build
@@ -12,12 +12,10 @@ fs = import('fs')
if fs.is_file('src/version.h')
message('Using existing version.h')
else
- git = find_program('git', native: true, required: true)
- version_h = vcs_tag(
- command: [git, 'describe', '--tags', '--always', '--match', 'v[0-9]*', '--dirty=+'],
- fallback: 'v0.0.1',
+ version_h = configure_file(
input: 'src/version.h.in',
output: 'version.h',
+ configuration : conf_data,
)
simplex_maya_files = simplex_maya_files + version_h
endif
diff --git a/src/python/meson.build b/src/python/meson.build
index 7aff3a2f..243c0736 100644
--- a/src/python/meson.build
+++ b/src/python/meson.build
@@ -1,6 +1,6 @@
-py = import('python')
-py_inst = py.find_installation('python3')
-py_dep = py_inst.dependency()
+pymod = import('python')
+py = pymod.find_installation('python3', pure : false)
+py_dep = py.dependency()
pysimplex_files = files(['pysimplex.cpp'])
rapidjson_dep = dependency('rapidjson')
@@ -10,11 +10,47 @@ if get_option('buildtype') == 'debug'
lapi = ''
endif
-pysimplex = py_inst.extension_module(
- 'pysimplex',
- pysimplex_files,
- dependencies : simplexlib_dep,
- install: true,
- install_dir : meson.global_source_root() / 'output_Python',
- limited_api : lapi,
-)
+py_wheel_build = get_option('python_wheel_build')
+py_script_build = get_option('python_script_build')
+
+if py_script_build
+ pyinstall_dir = meson.project_source_root() / 'scripts'
+elif py_wheel_build
+ pyinstall_dir = py.get_install_dir()
+else
+ pyinstall_dir = meson.project_source_root() / 'output_Python'
+endif
+
+if py_script_build or py_wheel_build
+ install_subdir(
+ 'simplexui',
+ install_dir: pyinstall_dir,
+ exclude_files: ['_version.py.in', 'meson.build'],
+ )
+ fs = import('fs')
+ if not fs.is_file('simplexui/_version.py')
+ version_py = configure_file(
+ input: 'simplexui/_version.py.in',
+ output: '_version.py',
+ install_dir: pyinstall_dir / 'simplexui',
+ configuration : conf_data,
+ )
+ endif
+endif
+
+if not py_script_build
+ if py_wheel_build
+ install_kwargs = {'subdir' : 'simplexui'}
+ else
+ install_kwargs = {'install_dir' : pyinstall_dir}
+ endif
+ pysimplex = py.extension_module(
+ 'pysimplex',
+ pysimplex_files,
+ dependencies : [simplexlib_dep, rapidjson_dep],
+ install: true,
+ limited_api : lapi,
+ kwargs : install_kwargs,
+ )
+endif
+
diff --git a/scripts/simplexui/__init__.py b/src/python/simplexui/__init__.py
similarity index 95%
rename from scripts/simplexui/__init__.py
rename to src/python/simplexui/__init__.py
index 4f2222fe..25d066cb 100644
--- a/scripts/simplexui/__init__.py
+++ b/src/python/simplexui/__init__.py
@@ -15,11 +15,10 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import absolute_import
+from ._version import __version__ # noqa: F401
SIMPLEX_UI = None
SIMPLEX_UI_ROOT = None
-__version__ = "v0.0.1-dev"
def runSimplexUI():
diff --git a/src/python/simplexui/_version.py.in b/src/python/simplexui/_version.py.in
new file mode 100644
index 00000000..d1117b81
--- /dev/null
+++ b/src/python/simplexui/_version.py.in
@@ -0,0 +1 @@
+__version__ = "@VCS_TAG@"
diff --git a/scripts/simplexui/channelBox.py b/src/python/simplexui/channelBox.py
similarity index 77%
rename from scripts/simplexui/channelBox.py
rename to src/python/simplexui/channelBox.py
index 3d826b81..5633c9ea 100644
--- a/scripts/simplexui/channelBox.py
+++ b/src/python/simplexui/channelBox.py
@@ -15,14 +15,12 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-""" The ChannelBox
+"""The ChannelBox
A Super-minimal ui for interacting with a Simplex System
Currently VERY WIP. Probably shouldn't have committed it to master, but whatever
"""
# pylint:disable=unused-import,relative-import,missing-docstring,unused-argument,no-self-use
-from __future__ import absolute_import, print_function
-
import os
import sys
@@ -53,11 +51,11 @@ class SlideFilter(QObject):
def __init__(self, parent):
super(SlideFilter, self).__init__(parent)
- self.slideCursor = Qt.SizeHorCursor
- self.slideButton = Qt.LeftButton
+ self.slideCursor = Qt.CursorShape.SizeHorCursor
+ self.slideButton = Qt.MouseButton.LeftButton
- self.fastModifier = Qt.ControlModifier
- self.slowModifier = Qt.ShiftModifier
+ self.fastModifier = Qt.KeyboardModifier.ControlModifier
+ self.slowModifier = Qt.KeyboardModifier.ShiftModifier
self.fastMultiplier = 5.0
self.slowDivisor = 5.0
@@ -97,13 +95,13 @@ def eventFilter(self, obj, event):
"""
if hasattr(self, "SLIDE_ENABLED"):
- if event.type() == QEvent.MouseButtonPress:
+ if event.type() == QEvent.Type.MouseButtonPress:
if event.button() & self.slideButton:
self.startSlide(obj, event)
self.doSlide(obj, event)
self._slideStart = True
- elif event.type() == QEvent.MouseMove:
+ elif event.type() == QEvent.Type.MouseMove:
if self._slideStart:
try:
self.doSlide(obj, event)
@@ -113,7 +111,7 @@ def eventFilter(self, obj, event):
raise # re-raise the exception
return True
- elif event.type() == QEvent.MouseButtonRelease:
+ elif event.type() == QEvent.Type.MouseButtonRelease:
if event.button() & self.slideButton:
self._pressed = False
self._slideStart = False
@@ -302,7 +300,7 @@ def paintSlider(self, delegate, slider, painter, rect, palette):
"""
painter.save()
try:
- painter.setRenderHint(QPainter.Antialiasing, True)
+ painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
fgColor = slider.color
bgColor = QColor(slider.color)
@@ -342,7 +340,7 @@ def paintSlider(self, delegate, slider, painter, rect, palette):
fgPath = fgPath.translated(rx, ry)
painter.fillPath(fgPath, fgBrush)
- opts = QTextOption(Qt.AlignCenter)
+ opts = QTextOption(Qt.AlignmentFlag.AlignCenter)
frect = QRectF(rx, ry, rw, rh)
painter.drawText(frect, slider.name, opts)
# painter.drawPath(bgPath)
@@ -386,7 +384,10 @@ def setChannels(self, channels):
self.channels = channels
self.endResetModel()
- def index(self, row, column=0, parIndex=QModelIndex()):
+ def index(self, row, column=0, parIndex=None):
+ if parIndex is None:
+ parIndex = QModelIndex()
+
try:
item = self.channels[row]
except IndexError:
@@ -407,17 +408,21 @@ def data(self, index, role):
return None
item = index.internalPointer()
- if role in (Qt.DisplayRole, Qt.EditRole):
+ if role in (Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole):
if isinstance(item, (Group, Slider)):
return item.name
- elif role == Qt.TextAlignmentRole:
- return Qt.AlignCenter
+ elif role == Qt.ItemDataRole.TextAlignmentRole:
+ return Qt.AlignmentFlag.AlignCenter
return None
def flags(self, index):
- return Qt.ItemIsEnabled | Qt.ItemIsEditable | Qt.ItemIsSelectable
+ return (
+ Qt.ItemFlag.ItemIsEnabled
+ | Qt.ItemFlag.ItemIsEditable
+ | Qt.ItemFlag.ItemIsSelectable
+ )
def itemFromIndex(self, index):
return index.internalPointer()
@@ -542,7 +547,7 @@ def getItemAppendRow(self, item):
return self.getItemRowCount(item)
def getItemData(self, item, column, role):
- if role in (Qt.DisplayRole, Qt.EditRole):
+ if role in (Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole):
if column == 0:
if isinstance(item, Group):
return item.name
@@ -632,77 +637,71 @@ def testSliderListDisplay(smpxPath):
simp = Simplex.buildSystemFromSmpx(smpxPath)
channels = []
- redAttrs = set(
- [
- "lowerLipDepressor_X",
- "stretcher_X",
- "platysmaFlex_X",
- "cheekRaiser_X",
- "jawOpen",
- "lidTightener_X",
- "outerBrowRaiser_X",
- "eyesClosed_X",
- "cornerPuller_X",
- "noseWrinkler_X",
- "lipsBlow_X",
- "cornerDepressor_X",
- "funneler",
- "browLateral_X",
- "innerBrowRaiser_X",
- "upperLipRaiser_X",
- "chinRaiser",
- "cheek_SuckBlow_X",
- "pucker",
- "eyeGaze_DownUp_X",
- "eyeGaze_RightLeft_X",
- "upperLidTweak_X",
- "lowerLidTweak_X",
- ]
- )
- greenAttrs = set(
- [
- "nasolabialDeepener_X",
- "neckStretcher_X",
- "lipsPressed_T",
- "lipsPressed_B",
- "throatCompress",
- "lipsRolled_InOut_B",
- "lipsRolled_InOut_T",
- "sharpCornerPuller_X",
- "dimpler_X",
- "eyeBlink_X",
- "scalpSlide_BackFwd",
- "browDown_X",
- "mouthSwing_RightLeft",
- "sternoFlex_X",
- "throatOpen",
- ]
- )
- blueAttrs = set(
- [
- "adamsApple",
- "noseSwing_RightLeft",
- "nostrilCompress_X",
- "jawThrust_BackFwd",
- "eyesWide_X",
- "lipsVerticalT_X",
- "lipsVerticalB_X",
- "earPull_X",
- "lipsTighten_T",
- "lipsTighten_B",
- "lipsCompress_T",
- "lipsCompress_B",
- "lipsShift_RightLeft_B",
- "lipsShift_RightLeft_T",
- "lipsNarrowT_X",
- "lipsNarrowB_X",
- "jawSwing_RightLeft",
- "nostril_SuckFlare_X",
- "lipsCorner_DownUp_X",
- "jawClench",
- ]
- )
- greyAttrs = set(["lipsTogether"])
+ redAttrs = {
+ "lowerLipDepressor_X",
+ "stretcher_X",
+ "platysmaFlex_X",
+ "cheekRaiser_X",
+ "jawOpen",
+ "lidTightener_X",
+ "outerBrowRaiser_X",
+ "eyesClosed_X",
+ "cornerPuller_X",
+ "noseWrinkler_X",
+ "lipsBlow_X",
+ "cornerDepressor_X",
+ "funneler",
+ "browLateral_X",
+ "innerBrowRaiser_X",
+ "upperLipRaiser_X",
+ "chinRaiser",
+ "cheek_SuckBlow_X",
+ "pucker",
+ "eyeGaze_DownUp_X",
+ "eyeGaze_RightLeft_X",
+ "upperLidTweak_X",
+ "lowerLidTweak_X",
+ }
+ greenAttrs = {
+ "nasolabialDeepener_X",
+ "neckStretcher_X",
+ "lipsPressed_T",
+ "lipsPressed_B",
+ "throatCompress",
+ "lipsRolled_InOut_B",
+ "lipsRolled_InOut_T",
+ "sharpCornerPuller_X",
+ "dimpler_X",
+ "eyeBlink_X",
+ "scalpSlide_BackFwd",
+ "browDown_X",
+ "mouthSwing_RightLeft",
+ "sternoFlex_X",
+ "throatOpen",
+ }
+ blueAttrs = {
+ "adamsApple",
+ "noseSwing_RightLeft",
+ "nostrilCompress_X",
+ "jawThrust_BackFwd",
+ "eyesWide_X",
+ "lipsVerticalT_X",
+ "lipsVerticalB_X",
+ "earPull_X",
+ "lipsTighten_T",
+ "lipsTighten_B",
+ "lipsCompress_T",
+ "lipsCompress_B",
+ "lipsShift_RightLeft_B",
+ "lipsShift_RightLeft_T",
+ "lipsNarrowT_X",
+ "lipsNarrowB_X",
+ "jawSwing_RightLeft",
+ "nostril_SuckFlare_X",
+ "lipsCorner_DownUp_X",
+ "jawClench",
+ }
+ greyAttrs = {"lipsTogether"}
app = QApplication(sys.argv)
tv = ChannelList()
@@ -710,7 +709,7 @@ def testSliderListDisplay(smpxPath):
delegate = ChannelBoxDelegate()
slideFilter = SlideFilter(tv.viewport())
- slideFilter.slideButton = Qt.LeftButton
+ slideFilter.slideButton = Qt.MouseButton.LeftButton
tv.viewport().installEventFilter(slideFilter)
slideFilter.slidePressed.connect(tv.slideStart)
slideFilter.slideReleased.connect(tv.slideStop)
@@ -752,77 +751,74 @@ def testSliderTreeDisplay(smpxPath):
"""
simp = Simplex.buildSystemFromSmpx(smpxPath)
- redAttrs = set(
- [
- "lowerLipDepressor_X",
- "stretcher_X",
- "platysmaFlex_X",
- "cheekRaiser_X",
- "jawOpen",
- "lidTightener_X",
- "outerBrowRaiser_X",
- "eyesClosed_X",
- "cornerPuller_X",
- "noseWrinkler_X",
- "lipsBlow_X",
- "cornerDepressor_X",
- "funneler",
- "browLateral_X",
- "innerBrowRaiser_X",
- "upperLipRaiser_X",
- "chinRaiser",
- "cheek_SuckBlow_X",
- "pucker",
- "eyeGaze_DownUp_X",
- "eyeGaze_RightLeft_X",
- "upperLidTweak_X",
- "lowerLidTweak_X",
- ]
- )
- greenAttrs = set(
- [
- "nasolabialDeepener_X",
- "neckStretcher_X",
- "lipsPressed_T",
- "lipsPressed_B",
- "throatCompress",
- "lipsRolled_InOut_B",
- "lipsRolled_InOut_T",
- "sharpCornerPuller_X",
- "dimpler_X",
- "eyeBlink_X",
- "scalpSlide_BackFwd",
- "browDown_X",
- "mouthSwing_RightLeft",
- "sternoFlex_X",
- "throatOpen",
- ]
- )
- blueAttrs = set(
- [
- "adamsApple",
- "noseSwing_RightLeft",
- "nostrilCompress_X",
- "jawThrust_BackFwd",
- "eyesWide_X",
- "lipsVerticalT_X",
- "lipsVerticalB_X",
- "earPull_X",
- "lipsTighten_T",
- "lipsTighten_B",
- "lipsCompress_T",
- "lipsCompress_B",
- "lipsShift_RightLeft_B",
- "lipsShift_RightLeft_T",
- "lipsNarrowT_X",
- "lipsNarrowB_X",
- "jawSwing_RightLeft",
- "nostril_SuckFlare_X",
- "lipsCorner_DownUp_X",
- "jawClench",
- ]
- )
- greyAttrs = set(["lipsTogether"])
+ _redAttrs = {
+ "lowerLipDepressor_X",
+ "stretcher_X",
+ "platysmaFlex_X",
+ "cheekRaiser_X",
+ "jawOpen",
+ "lidTightener_X",
+ "outerBrowRaiser_X",
+ "eyesClosed_X",
+ "cornerPuller_X",
+ "noseWrinkler_X",
+ "lipsBlow_X",
+ "cornerDepressor_X",
+ "funneler",
+ "browLateral_X",
+ "innerBrowRaiser_X",
+ "upperLipRaiser_X",
+ "chinRaiser",
+ "cheek_SuckBlow_X",
+ "pucker",
+ "eyeGaze_DownUp_X",
+ "eyeGaze_RightLeft_X",
+ "upperLidTweak_X",
+ "lowerLidTweak_X",
+ }
+
+ _greenAttrs = {
+ "nasolabialDeepener_X",
+ "neckStretcher_X",
+ "lipsPressed_T",
+ "lipsPressed_B",
+ "throatCompress",
+ "lipsRolled_InOut_B",
+ "lipsRolled_InOut_T",
+ "sharpCornerPuller_X",
+ "dimpler_X",
+ "eyeBlink_X",
+ "scalpSlide_BackFwd",
+ "browDown_X",
+ "mouthSwing_RightLeft",
+ "sternoFlex_X",
+ "throatOpen",
+ }
+
+ _blueAttrs = {
+ "adamsApple",
+ "noseSwing_RightLeft",
+ "nostrilCompress_X",
+ "jawThrust_BackFwd",
+ "eyesWide_X",
+ "lipsVerticalT_X",
+ "lipsVerticalB_X",
+ "earPull_X",
+ "lipsTighten_T",
+ "lipsTighten_B",
+ "lipsCompress_T",
+ "lipsCompress_B",
+ "lipsShift_RightLeft_B",
+ "lipsShift_RightLeft_T",
+ "lipsNarrowT_X",
+ "lipsNarrowB_X",
+ "jawSwing_RightLeft",
+ "nostril_SuckFlare_X",
+ "lipsCorner_DownUp_X",
+ "jawClench",
+ }
+
+ _greyAttrs = {"lipsTogether"}
app = QApplication(sys.argv)
# tv = ChannelTree()
@@ -831,7 +827,7 @@ def testSliderTreeDisplay(smpxPath):
# delegate = ChannelBoxDelegate()
# slideFilter = SlideFilter(tv.viewport())
- # slideFilter.slideButton = Qt.LeftButton
+ # slideFilter.slideButton = Qt.MouseButton.LeftButton
# tv.viewport().installEventFilter(slideFilter)
# slideFilter.slidePressed.connect(tv.slideStart)
# slideFilter.slideReleased.connect(tv.slideStop)
diff --git a/scripts/simplexui/comboCheckDialog.py b/src/python/simplexui/comboCheckDialog.py
similarity index 89%
rename from scripts/simplexui/comboCheckDialog.py
rename to src/python/simplexui/comboCheckDialog.py
index 5f8cdd23..ce330848 100644
--- a/scripts/simplexui/comboCheckDialog.py
+++ b/src/python/simplexui/comboCheckDialog.py
@@ -15,12 +15,8 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import absolute_import
-
from itertools import combinations, product
-from six.moves import range, zip
-
from .dragFilter import DragFilter
from .items import Combo, Slider
from Qt import QtCompat
@@ -178,7 +174,7 @@ def __init__(self, sliders, values=None, mode="create", parent=None):
self.valueDict = values or {}
self.setSliders(sliders)
if self.mode == "create":
- self.uiAutoUpdateCHK.setCheckState(Qt.Unchecked)
+ self.uiAutoUpdateCHK.setCheckState(Qt.CheckState.Unchecked)
self.uiAutoUpdateCHK.hide()
self.uiManualUpdateBTN.hide()
@@ -206,12 +202,12 @@ def dragTick(self, ticks, mul):
"""
items = self.uiEditTREE.selectedItems()
for item in items:
- val = item.data(3, Qt.EditRole)
+ val = item.data(3, Qt.ItemDataRole.EditRole)
val += (0.05) * ticks * mul
if abs(val) < 1.0e-5:
val = 0.0
val = max(min(val, 1.0), -1.0)
- item.setData(3, Qt.EditRole, val)
+ item.setData(3, Qt.ItemDataRole.EditRole, val)
self.uiEditTREE.viewport().update()
def setSliders(self, val):
@@ -228,22 +224,29 @@ def setSliders(self, val):
"""
self.uiEditTREE.clear()
dvs = [None, -1.0, 1.0, 0.5]
- roles = [Qt.UserRole, Qt.UserRole, Qt.UserRole, Qt.EditRole]
+ roles = [
+ Qt.ItemDataRole.UserRole,
+ Qt.ItemDataRole.UserRole,
+ Qt.ItemDataRole.UserRole,
+ Qt.ItemDataRole.EditRole,
+ ]
val = val or []
for slider in val:
item = QTreeWidgetItem(self.uiEditTREE, [slider.name])
- item.setFlags(item.flags() | Qt.ItemIsEditable)
+ item.setFlags(item.flags() | Qt.ItemFlag.ItemIsEditable)
vvv = self.valueDict.get(slider, [-1.0, 1.0])
mvs = [i for i in vvv if abs(i) != 1.0]
mvs = mvs[0] if mvs else 0.5
- item.setData(0, Qt.UserRole, slider)
+ item.setData(0, Qt.ItemDataRole.UserRole, slider)
for col in range(1, 4):
val = mvs if col == 3 else dvs[col]
item.setData(col, roles[col], val)
rng = slider.prog.getRange()
if val in rng or col == 3:
- chk = Qt.Checked if val in vvv else Qt.Unchecked
+ chk = (
+ Qt.CheckState.Checked if val in vvv else Qt.CheckState.Unchecked
+ )
item.setCheckState(col, chk)
for col in reversed(list(range(4))):
@@ -280,10 +283,15 @@ def _populate(self):
root = self.uiEditTREE.invisibleRootItem()
lockDict = {}
sliderList = []
- roles = [Qt.UserRole, Qt.UserRole, Qt.UserRole, Qt.EditRole]
+ roles = [
+ Qt.ItemDataRole.UserRole,
+ Qt.ItemDataRole.UserRole,
+ Qt.ItemDataRole.UserRole,
+ Qt.ItemDataRole.EditRole,
+ ]
for row in range(root.childCount()):
item = root.child(row)
- slider = item.data(0, Qt.UserRole)
+ slider = item.data(0, Qt.ItemDataRole.UserRole)
if slider is not None:
sliderList.append(slider)
lv = [
diff --git a/scripts/simplexui/commands/__init__.py b/src/python/simplexui/commands/__init__.py
similarity index 100%
rename from scripts/simplexui/commands/__init__.py
rename to src/python/simplexui/commands/__init__.py
diff --git a/scripts/simplexui/commands/alembicCommon.py b/src/python/simplexui/commands/alembicCommon.py
similarity index 88%
rename from scripts/simplexui/commands/alembicCommon.py
rename to src/python/simplexui/commands/alembicCommon.py
index 427da6d2..30302e54 100644
--- a/scripts/simplexui/commands/alembicCommon.py
+++ b/src/python/simplexui/commands/alembicCommon.py
@@ -15,14 +15,12 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-""" Alembic files can be difficult to work with, and can be *very* slow in Python
+"""Alembic files can be difficult to work with, and can be *very* slow in Python
This is a library of convenience functions with the numpy speed optimizations
"""
-from __future__ import absolute_import, print_function
import os
-import six
from alembic.Abc import IArchive, OArchive, OStringProperty
from alembic.AbcGeom import (
GeometryScope,
@@ -35,21 +33,17 @@
OXform,
)
from imath import IntArray, UnsignedIntArray, V2f, V2fArray, V3fArray
-from six.moves import range, zip
try:
import numpy as np
+ from .numpytoimath import imathToNumpy, numpyToImath
except ImportError:
np = None
- arrayToNumpy = None
-else:
- try:
- from imathnumpy import arrayToNumpy # pylint:disable=no-name-in-module
- except ImportError:
- arrayToNumpy = None
+ numpyToImath = None
+ imathToNumpy = None
-def pbPrint(pBar, message=None, val=None, maxVal=None, _pbPrintLastComma=[]):
+def pbPrint(pBar, message=None, val=None, maxVal=None, _pbPrintLastComma=None):
"""A function that handles displaying messages in a QProgressDialog or printing to stdout
Don't forget to call QApplication.processEvents() after using this function
@@ -67,6 +61,8 @@ def pbPrint(pBar, message=None, val=None, maxVal=None, _pbPrintLastComma=[]):
_pbPrintLastComma: object
INTERNAL USE ONLY
"""
+ _pbPrintLastComma = [] if _pbPrintLastComma is None else _pbPrintLastComma
+
if pBar is not None:
if val is not None:
pBar.setValue(val)
@@ -111,22 +107,12 @@ def mkArray(aType, iList):
if isinstance(iList, aType):
return iList
- if np is None:
+ if np is None or numpyToImath is None:
array = aType(len(iList))
for i in range(len(iList)):
array[i] = tuple(iList[i])
return array
- elif arrayToNumpy is None:
- array = aType(len(iList))
- for i in range(len(iList)):
- array[i] = tuple(iList[i].tolist())
- return array
- else:
- iList = np.array(iList)
- array = aType(len(iList))
- memView = arrayToNumpy(array)
- np.copyto(memView, iList)
- return array
+ return numpyToImath(iList, aType)
def mk1dArray(aType, iList):
@@ -146,19 +132,14 @@ def mk1dArray(aType, iList):
"""
if isinstance(iList, aType):
return iList
- if np is None or arrayToNumpy is None or aType is UnsignedIntArray:
+ if np is None or numpyToImath is None:
array = aType(len(iList))
for i in range(len(iList)):
# Gotta cast to int because an "int" from numpy has
# the type np.int32, which makes this conversion angry
array[i] = int(iList[i])
return array
- else:
- iList = np.array(iList)
- array = aType(len(iList))
- memView = arrayToNumpy(array)
- np.copyto(memView, iList)
- return array
+ return numpyToImath(iList, aType)
def mkSampleVertexPoints(pts):
@@ -307,18 +288,11 @@ def getSampleArray(imesh, pBar=None):
meshSchema = imesh.getSchema()
posProp = meshSchema.getPositionsProperty()
numShapes = len(posProp.samples)
- if arrayToNumpy is not None:
+ if imathToNumpy is not None and np is not None:
shapes = np.empty((len(posProp.samples), len(posProp.samples[0]), 3))
for i, s in enumerate(posProp.samples):
pbPrint(pBar, message="Reading Shape", val=i, maxVal=numShapes)
- shapes[i] = arrayToNumpy(s)
- elif np is not None:
- shapes = []
- for i, s in enumerate(posProp.samples):
- pbPrint(pBar, message="Reading Shape", val=i, maxVal=numShapes)
- shapes.append((list(s.x), list(s.y), list(s.z)))
- shapes = np.array(shapes)
- shapes = shapes.transpose((0, 2, 1))
+ shapes[i] = imathToNumpy(s)
else:
shapes = []
for i, s in enumerate(posProp.samples):
@@ -365,13 +339,11 @@ def getStaticMeshArrays(imesh):
The number of vertices per face as np.array if possible
"""
faces, counts = getStaticMeshData(imesh)
- if arrayToNumpy is not None:
- faces = arrayToNumpy(faces).copy()
- counts = arrayToNumpy(counts).copy()
- elif np is not None:
- faces, counts = np.array(faces), np.array(counts)
- else:
+ if np is None or imathToNumpy is None:
faces, counts = list(faces), list(counts)
+ else:
+ faces = imathToNumpy(faces)
+ counts = imathToNumpy(counts)
return faces, counts
@@ -456,20 +428,17 @@ def getFlatUvFaces(imesh):
if iuvs.isIndexed():
indexed = True
idxs = iuvs.getIndexProperty().getValue()
- # if arrayToNumpy is not None:
- # idxs = arrayToNumpy(idxs).copy()
- # elif np is not None:
- if np is not None:
- idxs = np.array(idxs)
- else:
+ if imathToNumpy is None or np is None:
idxs = list(idxs)
+ else:
+ idxs = imathToNumpy(idxs)
else:
indexed = False
rawCount = sum(list(sch.getFaceCountsProperty().samples[0]))
- if np is not None:
- idxs = np.arange(rawCount)
- else:
+ if np is None:
idxs = list(range(rawCount))
+ else:
+ idxs = np.arange(rawCount)
return idxs, indexed
@@ -709,29 +678,6 @@ def flattenFaces(faces):
return faceCounts, faceIdxs
-def unflattenFaces(faces, counts):
- """Take a flat face/count representation of faces
- and turn it into a nested list representation
-
- Parameters
- ----------
- faces : np.array
- The flat list of face connectivity
- counts : np.array
- The flat list of vertices per face
-
- Returns
- -------
- : [[int, ...], ...]
- The nested list representation
- """
- out, ptr = [], 0
- for c in counts:
- out.append(faces[ptr : ptr + c].tolist())
- ptr += c
- return out
-
-
def buildAbc(
outPath,
points,
@@ -812,7 +758,7 @@ def buildAbc(
opar = OXform(parent, str(name + transformSuffix))
if propDict:
props = opar.getSchema().getUserProperties()
- for k, v in six.iteritems(propDict):
+ for k, v in propDict.items():
writeStringProperty(props, str(k), str(v), ogawa=ogawa)
omesh = OPolyMesh(opar, str(name + shapeSuffix))
@@ -966,7 +912,7 @@ def buildSmpx(
name=name,
shapeSuffix="",
transformSuffix="",
- propDict=dict(simplex=jsString),
+ propDict={"simplex": jsString},
ogawa=ogawa,
pBar=pBar,
)
diff --git a/scripts/simplexui/commands/applyCorrectives.py b/src/python/simplexui/commands/applyCorrectives.py
similarity index 95%
rename from scripts/simplexui/commands/applyCorrectives.py
rename to src/python/simplexui/commands/applyCorrectives.py
index 1e630e64..226d408f 100644
--- a/scripts/simplexui/commands/applyCorrectives.py
+++ b/src/python/simplexui/commands/applyCorrectives.py
@@ -16,13 +16,10 @@
# along with Simplex. If not, see .
# pylint:disable=unused-variable
-from __future__ import absolute_import, print_function
-
import itertools
import os
from pysimplex import PySimplex
-from six.moves import map, zip
from ..items import Combo, Simplex, Slider
from Qt.QtWidgets import QApplication
@@ -220,7 +217,7 @@ def buildFullShapes(simplex, shapeObjs, shapes, solver, pBar=None):
indexBySlider = {s: i for i, s in enumerate(simplex.sliders)}
indexByShape = {s: i for i, s in enumerate(simplex.shapes)}
floaters = set(simplex.getFloatingShapes())
- floatIdxs = set([indexByShape[s] for s in floaters])
+ floatIdxs = {indexByShape[s] for s in floaters}
shapeDict = {}
for item in itertools.chain(simplex.sliders, simplex.combos):
@@ -306,7 +303,6 @@ def collapseFullShapes(simplex, allPts, ptsByShape, vecByShape, pBar=None):
QApplication.processEvents()
for shpOrderIdx, shape in enumerate(shapeOrder):
-
if pBar is not None:
pBar.setValue(shpOrderIdx)
pBar.setLabelText("Building Corrected Deltas\n{}".format(shape.name))
diff --git a/scripts/simplexui/commands/correctiveInterface.py b/src/python/simplexui/commands/correctiveInterface.py
similarity index 90%
rename from scripts/simplexui/commands/correctiveInterface.py
rename to src/python/simplexui/commands/correctiveInterface.py
index e9245078..2470ee2e 100644
--- a/scripts/simplexui/commands/correctiveInterface.py
+++ b/src/python/simplexui/commands/correctiveInterface.py
@@ -15,11 +15,6 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import absolute_import
-
-import six
-from six.moves import zip
-
from Qt.QtWidgets import QApplication
try:
@@ -27,14 +22,9 @@
except ImportError:
pass
-try:
- from .mayaCorrectiveInterface import getShiftValues, resetPose, setPose
-
- dcc = "maya"
-except ImportError:
- from .xsiCorrectiveInterface import getShiftValues, resetPose, setPose
+from .mayaCorrectiveInterface import getShiftValues, resetPose, setPose
- dcc = "xsi"
+dcc = "maya"
def getRefForPoses(mesh, poses, multipliers):
@@ -197,14 +187,14 @@ def buildCorrectiveReferences(mesh, simplex, poses, sliders, pBar=None):
pBar.setLabelText("Building Combo References")
pBar.setValue(0)
mv = 0
- for combo in six.iterkeys(sliderValuesByCombo):
+ for combo in sliderValuesByCombo:
for p in combo.prog.pairs:
if not p.shape.isRest:
mv += 1
pBar.setMaximum(mv)
QApplication.processEvents()
- for combo, sliderVals in six.iteritems(sliderValuesByCombo):
+ for combo, sliderVals in sliderValuesByCombo.items():
# components = frozenset(sliderVals)
poses = [poseBySlider[s] for s, _ in sliderVals]
for p in combo.prog.pairs:
@@ -261,7 +251,7 @@ def outputCorrectiveReferences(
if pBar is not None:
pBar.setLabelText("Writing Names")
QApplication.processEvents()
- nameWrite = ["{};{}".format(s.name, r) for s, r, in zip(shapes, refIdxs)]
+ nameWrite = ["{};{}".format(s.name, r) for s, r in zip(shapes, refIdxs)]
with open(outNames, "w") as f:
f.write("\n".join(nameWrite))
diff --git a/scripts/simplexui/commands/expandedExport.py b/src/python/simplexui/commands/expandedExport.py
similarity index 92%
rename from scripts/simplexui/commands/expandedExport.py
rename to src/python/simplexui/commands/expandedExport.py
index 551d9a18..1f7411e8 100644
--- a/scripts/simplexui/commands/expandedExport.py
+++ b/src/python/simplexui/commands/expandedExport.py
@@ -15,19 +15,16 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import absolute_import
-
-import six
from pysimplex import PySimplex
from ..interface.mayaInterface import DCC, disconnected
-from ..items import Combo, Slider, Traversal
+from ..items import Combo, Slider, Traversal, Simplex
from .alembicCommon import buildSmpx
try:
import numpy as np
except ImportError:
- pass
+ np = None
def _setSliders(ctrl, val, svs):
@@ -67,7 +64,7 @@ def setSliderGroup(ctrls, val):
for ctrl in ctrls:
_setSliders(ctrl, val, svs)
- for smpx, (slis, vals) in six.iteritems(svs):
+ for smpx, (slis, vals) in svs.items():
smpx.setSlidersWeights(slis, vals)
@@ -294,6 +291,9 @@ def parseExpandedData(smpx, restShape, sliderShapes, comboShapes, travShapes):
The point positions for a new set of shapes
"""
+ if np is None:
+ raise RuntimeError("Numpy is not available")
+
solver = PySimplex(smpx.dump())
shapeArray = np.zeros((len(smpx.shapes), len(restShape), 3))
@@ -301,13 +301,13 @@ def parseExpandedData(smpx, restShape, sliderShapes, comboShapes, travShapes):
indexByShape = {s: i for i, s in enumerate(smpx.shapes)}
floatShapeSet = set(smpx.getFloatingShapes())
- floatIdxs = sorted(set([indexByShape[s] for s in floatShapeSet]))
- travShapeSet = set([pp.shape for t in smpx.traversals for pp in t.prog.pairs])
- travIdxs = sorted(set([indexByShape[s] for s in travShapeSet]))
+ floatIdxs = sorted({indexByShape[s] for s in floatShapeSet})
+ travShapeSet = {pp.shape for t in smpx.traversals for pp in t.prog.pairs}
+ travIdxs = sorted({indexByShape[s] for s in travShapeSet})
# Sliders are simple, just set their shapes directly
- for ppDict in six.itervalues(sliderShapes):
- for pp, shp in six.iteritems(ppDict):
+ for ppDict in sliderShapes.values():
+ for pp, shp in ppDict.items():
shapeArray[indexByShape[pp.shape]] = shp - restShape
# First sort the combos by depth
@@ -317,7 +317,7 @@ def parseExpandedData(smpx, restShape, sliderShapes, comboShapes, travShapes):
for depth in sorted(comboByDepth.keys()):
for combo in comboByDepth[depth]:
- for pp, shp in six.iteritems(comboShapes[combo]):
+ for pp, shp in comboShapes[combo].items():
inVec = _buildSolverInputs(smpx, combo, pp.value, indexBySlider)
outVec = np.array(solver.solve(inVec))
outVec[np.where(np.isclose(outVec, 0.0))] = 0.0
@@ -342,7 +342,7 @@ def parseExpandedData(smpx, restShape, sliderShapes, comboShapes, travShapes):
for depth in sorted(travByDepth.keys()):
for trav in travByDepth[depth]:
- for pp, shp in six.iteritems(travShapes[trav]):
+ for pp, shp in travShapes[trav].items():
inVec = _buildSolverInputs(smpx, trav, pp.value, indexBySlider)
outVec = np.array(solver.solve(inVec))
outVec[np.where(np.isclose(outVec, 0.0))] = 0.0
@@ -429,6 +429,7 @@ def expandedExportAbc(path, mesh, master, clients=()):
if __name__ == "__main__":
# get the smpx from the UI
+
master = Simplex.buildSystemFromMesh("Face_SIMPLEX", "Face")
client = Simplex.buildSystemFromMesh("Face_SIMPLEX2", "Face2")
outPath = r"D:\Users\tyler\Desktop\TEST\expanded.smpx"
diff --git a/scripts/simplexui/commands/hdf5Convert.py b/src/python/simplexui/commands/hdf5Convert.py
similarity index 94%
rename from scripts/simplexui/commands/hdf5Convert.py
rename to src/python/simplexui/commands/hdf5Convert.py
index 32ac5ab4..17194d0f 100644
--- a/scripts/simplexui/commands/hdf5Convert.py
+++ b/src/python/simplexui/commands/hdf5Convert.py
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import absolute_import
-
import json
from .alembicCommon import buildSmpx, readSmpx
diff --git a/scripts/simplexui/commands/mayaCorrectiveInterface.py b/src/python/simplexui/commands/mayaCorrectiveInterface.py
similarity index 93%
rename from scripts/simplexui/commands/mayaCorrectiveInterface.py
rename to src/python/simplexui/commands/mayaCorrectiveInterface.py
index bf34e019..f4258139 100644
--- a/scripts/simplexui/commands/mayaCorrectiveInterface.py
+++ b/src/python/simplexui/commands/mayaCorrectiveInterface.py
@@ -15,15 +15,12 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-""" Get the corrective deltas from a rig in Maya """
-
-from __future__ import absolute_import
+"""Get the corrective deltas from a rig in Maya"""
from ctypes import c_float
from maya import OpenMaya as om
from maya import cmds
-from six.moves import range
try:
import numpy as np
@@ -53,7 +50,7 @@ def resetPose(pvp):
pvp : [(str, float), ...]
A list of property/value pairs
"""
- for prop, val in pvp:
+ for prop, _val in pvp:
cmds.setAttr(prop, 0)
diff --git a/src/python/simplexui/commands/mayatonumpy.py b/src/python/simplexui/commands/mayatonumpy.py
new file mode 100644
index 00000000..95f3f4ed
--- /dev/null
+++ b/src/python/simplexui/commands/mayatonumpy.py
@@ -0,0 +1,375 @@
+from maya import OpenMaya as om
+import numpy as np
+from ctypes import c_float, c_double, c_int, c_uint
+
+# fmt: off
+_CONVERT_DICT = {
+ om.MPointArray: (float, 4, c_double, om.MScriptUtil.asDouble4Ptr),
+ om.MFloatPointArray: (float, 4, c_float , om.MScriptUtil.asFloat4Ptr),
+ om.MVectorArray: (float, 3, c_double, om.MScriptUtil.asDouble3Ptr),
+ om.MFloatVectorArray: (float, 3, c_float , om.MScriptUtil.asFloat3Ptr),
+ om.MDoubleArray: (float, 1, c_double, om.MScriptUtil.asDoublePtr),
+ om.MFloatArray: (float, 1, c_float , om.MScriptUtil.asFloatPtr),
+ om.MIntArray: (int , 1, c_int , om.MScriptUtil.asIntPtr),
+ om.MUintArray: (int , 1, c_uint , om.MScriptUtil.asUintPtr),
+}
+# fmt: on
+
+
+def _swigConnect(mArray, count, util):
+ """
+ Use an MScriptUtil to build SWIG array that we can read from and write to.
+ Make sure to get the MScriptUtil from outside this function, otherwise
+ it may be garbage collected
+ The _CONVERT_DICT holds {mayaType: (pyType, numComps, cType, ptrType)} where
+ pyType: The type that is used to fill the MScriptUtil array.
+ numComps: The number of components. So a double4Ptr would be 4
+ cType: The ctypes type used to read the data
+ ptrType: An unbound method on MScriptUtil to cast the pointer to the correct type
+ I can still call that unbound method by manually passing the usually-implicit
+ self argument (which will be an instance of MScriptUtil)
+ """
+ pyTyp, comps, ctp, ptrTyp = _CONVERT_DICT[type(mArray)]
+ cc = count * comps
+ util.createFromList([pyTyp()] * cc, cc)
+
+ # passing util as 'self' to call the unbound method
+ ptr = ptrTyp(util)
+ mArray.get(ptr)
+
+ if comps == 1:
+ cdata = ctp * count
+ else:
+ # Multiplication follows some strange rules here
+ # I would expect (ctype*3)*N to be an Nx3 array (ctype*3 repeated N times)
+ # However, it gets converted to a 3xN array
+ cdata = (ctp * comps) * count
+
+ # int(ptr) gives the memory address
+ cta = cdata.from_address(int(ptr))
+
+ # This makes numpy look at the same memory as the ctypes array
+ # so we can both read from and write to that data through numpy
+ npArray = np.ctypeslib.as_array(cta)
+ return npArray, ptr
+
+
+def _swigConnectMatrix(mat, ctp):
+ # With a matrix, you can just get the double[4][4] without an MScriptUtil
+ ptr = mat.matrix
+ cdata = ctp * 4 * 4
+
+ # int(ptr) gives the memory address
+ cta = cdata.from_address(int(ptr))
+
+ # This makes numpy look at the same memory as the ctypes array
+ # so we can both read from and write to that data through numpy
+ npArray = np.ctypeslib.as_array(cta)
+ return npArray, ptr
+
+
+def mayaToNumpy(mArray):
+ """Convert a maya array to a numpy array
+
+ Parameters
+ ----------
+ ary : MArray
+ The maya array to convert to a numpy array
+
+ Returns
+ -------
+ : np.array :
+ A numpy array that contains the data from mArray
+
+ """
+ if isinstance(mArray, om.MMatrix):
+ npArray, _ = _swigConnectMatrix(mArray, c_double)
+ elif isinstance(mArray, om.MFloatMatrix):
+ npArray, _ = _swigConnectMatrix(mArray, c_float)
+ else:
+ util = om.MScriptUtil()
+ count = mArray.length()
+ npArray, _ = _swigConnect(mArray, count, util)
+ return np.copy(npArray)
+
+
+def numpyToMaya(ary, mType):
+ """Convert a numpy array to a specific maya type array
+
+ Parameters
+ ----------
+ ary : np.array
+ The numpy array to convert to a maya array
+ mType : type
+ The maya type to convert to out of: MPointArray, MFloatPointArray, MVectorArray,
+ MFloatVectorArray, MDoubleArray, MFloatArray, MIntArray, MUintArray
+
+ Returns
+ -------
+ : mType :
+ An array of the provided type that contains the data from ary
+
+ """
+ # Handle matrices separately
+ if mType in (om.MMatrix, om.MFloatMatrix):
+ ctp = c_double if mType == om.MMatrix else c_float
+ if ary.shape != (4, 4):
+ msg = "Numpy array must have the proper shape. For matrix types that shape must be (4, 4). Got {0}"
+ raise ValueError(msg.format(ary.shape))
+ tmpMat = mType()
+ npArray, ptr = _swigConnectMatrix(tmpMat, ctp)
+ np.copyto(npArray, ary)
+ return mType(ptr)
+
+ # Add a little shape checking
+ comps = _CONVERT_DICT[mType][1]
+ if comps == 1:
+ if len(ary.shape) != 1:
+ raise ValueError("Numpy array must be 1D to convert to the given maya type")
+ else:
+ if len(ary.shape) != 2:
+ raise ValueError("Numpy array must be 2D to convert to the given maya type")
+ if ary.shape[1] != comps:
+ msg = "Numpy array must have the proper shape. Dimension 2 has size {0}, but needs size {1}"
+ raise ValueError(msg.format(ary.shape[1], comps))
+ count = ary.shape[0]
+ tmpAry = mType(count)
+ util = om.MScriptUtil()
+ npArray, ptr = _swigConnect(tmpAry, count, util)
+ np.copyto(npArray, ary)
+ return mType(ptr, count)
+
+
+# fmt: off
+_NTYPE_DICT = {
+ om.MFnNumericData.kInvalid: (om.MDataHandle.asDouble, om.MDataHandle.setDouble),
+ om.MFnNumericData.kFloat: (om.MDataHandle.asDouble, om.MDataHandle.setDouble),
+ om.MFnNumericData.kDouble: (om.MDataHandle.asDouble, om.MDataHandle.setDouble),
+ om.MFnNumericData.kByte: (om.MDataHandle.asInt, om.MDataHandle.setInt),
+ om.MFnNumericData.kChar: (om.MDataHandle.asChar, om.MDataHandle.setChar),
+ om.MFnNumericData.kShort: (om.MDataHandle.asShort, om.MDataHandle.setShort),
+ om.MFnNumericData.kInt: (om.MDataHandle.asInt, om.MDataHandle.setInt),
+ #om.MFnNumericData.kInt64: (om.MDataHandle.asInt, om.MDataHandle.setInt64),
+ om.MFnNumericData.kAddr: (om.MDataHandle.asInt, om.MDataHandle.setInt),
+ om.MFnNumericData.kLong: (om.MDataHandle.asInt, om.MDataHandle.setInt),
+ om.MFnNumericData.kBoolean: (om.MDataHandle.asBool, om.MDataHandle.setBool),
+
+ om.MFnNumericData.k2Short: (om.MDataHandle.asShort2, om.MDataHandle.set2Short),
+ om.MFnNumericData.k2Long: (om.MDataHandle.asInt2, om.MDataHandle.set2Int),
+ om.MFnNumericData.k2Int: (om.MDataHandle.asInt2, om.MDataHandle.set2Int),
+ om.MFnNumericData.k3Short: (om.MDataHandle.asShort3, om.MDataHandle.set3Short),
+ om.MFnNumericData.k3Long: (om.MDataHandle.asInt3, om.MDataHandle.set3Int),
+ om.MFnNumericData.k3Int: (om.MDataHandle.asInt3, om.MDataHandle.set3Int),
+ om.MFnNumericData.k2Float: (om.MDataHandle.asFloat2, om.MDataHandle.set2Float),
+ om.MFnNumericData.k2Double: (om.MDataHandle.asDouble2, om.MDataHandle.set2Double),
+ om.MFnNumericData.k3Float: (om.MDataHandle.asFloat3, om.MDataHandle.set3Float),
+ om.MFnNumericData.k3Double: (om.MDataHandle.asDouble3, om.MDataHandle.set3Double),
+}
+
+_DTYPE_DICT = {
+ om.MFn.kPointArrayData: (om.MFnPointArrayData, om.MPointArray),
+ om.MFn.kDoubleArrayData: (om.MFnDoubleArrayData, om.MDoubleArray),
+ om.MFn.kFloatArrayData: (om.MFnFloatArrayData, om.MFloatArray),
+ om.MFn.kIntArrayData: (om.MFnIntArrayData, om.MIntArray),
+ om.MFn.kUInt64ArrayData: (om.MFnUInt64ArrayData, om.MPointArray),
+ om.MFn.kVectorArrayData: (om.MFnVectorArrayData, om.MVectorArray),
+}
+# fmt: on
+
+
+def getNumpyAttr(attrName):
+ """Read attribute data directly from the plugs into numpy
+
+ This function will read most numeric data types directly into numpy arrays
+ However, some simple data types (floats, vectors, etc...) have api accessors
+ that return python tuples. These will not be turned into numpy arrays.
+ And really, if you're getting simple data like that, just use cmds.getAttr
+
+ Parameters
+ ----------
+ attrName : str or om.MPlug
+ The name of the attribute to get (For instance "pSphere2.translate", or "group1.pim[0]")
+ Or the MPlug itself
+
+ Returns
+ -------
+ : object :
+ The numerical data from the provided plug. A np.array, float, int, or tuple
+
+ """
+ if isinstance(attrName, str):
+ sl = om.MSelectionList()
+ sl.add(attrName)
+ plug = om.MPlug()
+ sl.getPlug(0, plug)
+ elif isinstance(attrName, om.MPlug):
+ plug = attrName
+ else:
+ raise ValueError("AttrName is not a name or a plug")
+
+ # First just check if the data is numeric
+ mdh = plug.asMDataHandle()
+ if mdh.isNumeric():
+ # So, at this point, you should really just use getattr
+ ntype = mdh.numericType()
+ if ntype in _NTYPE_DICT:
+ return _NTYPE_DICT[ntype][0](mdh)
+ elif ntype == om.MFnNumericData.k4Double:
+ NotImplementedError("Haven't implemented double4 access yet")
+ else:
+ raise RuntimeError(
+ "I don't know how to access data from the given attribute"
+ )
+ else:
+ # The data is more complex than a simple number.
+ try:
+ pmo = plug.asMObject()
+ except RuntimeError as e:
+ # raise a more descriptive error. And make sure to actually print the plug name
+ raise RuntimeError(
+ "I don't know how to access data from the given attribute"
+ ) from e
+ apiType = pmo.apiType()
+
+ # A list of types that I can just pass to mayaToNumpy
+ if apiType in _DTYPE_DICT:
+ fn, dtype = _DTYPE_DICT[apiType]
+ fnPmo = fn(pmo)
+ ary = fnPmo.array()
+ return mayaToNumpy(ary)
+
+ elif apiType == om.MFn.kComponentListData:
+ fnPmo = om.MFnComponentListData(pmo)
+ mirs = []
+ mir = om.MIntArray()
+ for attrIndex in range(fnPmo.length()):
+ fnEL = om.MFnSingleIndexedComponent(fnPmo[attrIndex])
+ fnEL.getElements(mir)
+ mirs.append(mayaToNumpy(mir))
+ if not mirs:
+ return np.array([], dtype=int)
+ return np.concatenate(mirs)
+
+ elif apiType == om.MFn.kMatrixData:
+ fnPmo = om.MFnMatrixData(pmo)
+ mat = fnPmo.matrix()
+ return mayaToNumpy(mat)
+ else:
+ apiTypeStr = pmo.apiTypeStr()
+ raise NotImplementedError(
+ "I don't know how to handle {0} yet".format(apiTypeStr)
+ )
+ raise NotImplementedError("Fell all the way through")
+
+
+def setNumpyAttr(attrName, value):
+ """Write a numpy array directly into a maya plug
+
+ This function will handle most numeric plug types.
+ But for single float, individual point, etc.. types, consider using cmds.setAttr
+
+ THIS DOES NOT SUPPORT UNDO
+
+ Parameters
+ ----------
+ attrName : str or om.MPlug
+ The name of the attribute to get (For instance "pSphere2.translate", or "group1.pim[0]")
+ Or the MPlug itself
+ value : int, float, tuple, np.array
+ The correctly typed value to set on the attribute
+ """
+ if isinstance(attrName, str):
+ sl = om.MSelectionList()
+ sl.add(attrName)
+ plug = om.MPlug()
+ sl.getPlug(0, plug)
+ elif isinstance(attrName, om.MPlug):
+ plug = attrName
+ else:
+ raise ValueError("Data must be string or MPlug. Got {0}".format(type(attrName)))
+
+ # First just check if the data is numeric
+ mdh = plug.asMDataHandle()
+ if mdh.isNumeric():
+ # So, at this point, you should really just use setattr
+ ntype = mdh.numericType()
+ if ntype in _NTYPE_DICT:
+ _NTYPE_DICT[ntype][1](mdh, *value)
+ plug.setMObject(mdh.data())
+ elif ntype == om.MFnNumericData.k4Double:
+ NotImplementedError("Haven't implemented double4 access yet")
+ else:
+ raise RuntimeError("I don't know how to set data on the given attribute")
+ else:
+ # The data is more complex than a simple number.
+ try:
+ pmo = plug.asMObject()
+ except RuntimeError as e:
+ # raise a more descriptive error. And make sure to actually print the plug name
+ raise RuntimeError(
+ "I don't know how to access data from the given attribute"
+ ) from e
+ apiType = pmo.apiType()
+
+ if apiType in _DTYPE_DICT:
+ # build the pointArrayData
+ fnType, mType = _DTYPE_DICT[apiType]
+ fn = fnType()
+ mPts = numpyToMaya(value, mType)
+ dataObj = fn.create(mPts)
+ plug.setMObject(dataObj)
+ return
+
+ elif apiType == om.MFn.kComponentListData:
+ fnCompList = om.MFnComponentListData()
+ compList = fnCompList.create()
+ fnIdx = om.MFnSingleIndexedComponent()
+ idxObj = fnIdx.create(om.MFn.kMeshVertComponent)
+ mIdxs = numpyToMaya(value, om.MIntArray)
+ fnIdx.addElements(mIdxs)
+ fnCompList.add(idxObj)
+ plug.setMObject(compList)
+ return
+ else:
+ apiTypeStr = pmo.apiTypeStr()
+ raise NotImplementedError(
+ "I don't know how to handle {0} yet".format(apiTypeStr)
+ )
+
+ raise NotImplementedError("WTF? How did you get here??")
+
+
+################################################################################
+
+
+def test():
+ import time
+ from maya import cmds
+
+ meshName = "pSphere1"
+ bsName = "blendShape1"
+ meshIdx = 0
+ bsIdx = 0
+
+ # A quick test showing how to build a numpy array
+ # containing the deltas for a shape on a blendshape node
+ numVerts = cmds.polyEvaluate(meshName, vertex=True)
+ baseAttr = "{0}.it[{1}].itg[{2}].iti[6000]".format(bsName, meshIdx, bsIdx)
+ inPtAttr = baseAttr + ".inputPointsTarget"
+ inCompAttr = baseAttr + ".inputComponentsTarget"
+
+ start = time.time()
+ points = getNumpyAttr(inPtAttr)
+ idxs = getNumpyAttr(inCompAttr)
+ ret = np.zeros((numVerts, 4))
+ ret[idxs] = points
+ end = time.time()
+
+ print("IDXS", idxs.shape)
+ print("OUT", points.shape)
+ print("RET", ret.shape)
+ print("TOOK", end - start)
+
+
+if __name__ == "__main__":
+ test()
diff --git a/scripts/simplexui/commands/mesh.py b/src/python/simplexui/commands/mesh.py
similarity index 98%
rename from scripts/simplexui/commands/mesh.py
rename to src/python/simplexui/commands/mesh.py
index 4907f693..b6a8e23a 100644
--- a/scripts/simplexui/commands/mesh.py
+++ b/src/python/simplexui/commands/mesh.py
@@ -28,11 +28,6 @@
VertSet and FaceSet classes are just sets that also contain references back to the mesh
"""
-from __future__ import absolute_import
-
-import six
-from six.moves import range, zip
-
class Mesh(object):
"""
@@ -349,7 +344,7 @@ def _linkPairs(pairs):
while fwPairs:
linked = []
# pick a random start from whatever's left
- nxt = next(six.iterkeys(fwPairs))
+ nxt = next(fwPairs.keys())
while nxt is not None:
# Follow the pairs around until I cant find more
nnxt = fwPairs.pop(nxt, None)
@@ -359,7 +354,7 @@ def _linkPairs(pairs):
if fwPairs and linked[0][0] != linked[-1][0]:
# if there's still some left and we didn't find a cycle
# then search backwards
- bkPairs = {j: i for i, j in six.iteritems(fwPairs)}
+ bkPairs = {j: i for i, j in fwPairs.items()}
inv = []
nxt = linked[0][0]
while nxt is not None:
@@ -370,7 +365,7 @@ def _linkPairs(pairs):
# reverse and remove the extra (idx, None) pair
linked = inv[-2::-1] + linked
# Rebuild what's left into a new dict for the next group
- fwPairs = {j: i for i, j in six.iteritems(bkPairs)}
+ fwPairs = {j: i for i, j in bkPairs.items()}
# Parse the final output
fin = [i for i, _ in linked]
@@ -588,7 +583,7 @@ def getBorderVerts(self):
VertSet of border vertices
"""
out = VertSet(self)
- for edge, adj in six.iteritems(self.faceEdgeAdjacency):
+ for edge, adj in self.faceEdgeAdjacency.items():
if None in adj:
out.update(edge)
return out
@@ -913,7 +908,7 @@ def inner(self, *args):
return super(MeshSetMeta, mcs).__new__(mcs, clsName, bases, dct)
-class MeshSet(six.with_metaclass(MeshSetMeta, set)):
+class MeshSet(set, metaclass=MeshSetMeta):
"""An set-like object that deals with geometry"""
def __init__(self, mesh, indices=None):
diff --git a/src/python/simplexui/commands/numpytoimath.py b/src/python/simplexui/commands/numpytoimath.py
new file mode 100644
index 00000000..4a16b230
--- /dev/null
+++ b/src/python/simplexui/commands/numpytoimath.py
@@ -0,0 +1,319 @@
+import imath
+import ctypes
+import numpy as np
+from typing import TypeVar, Type
+
+
+NTYPEDICT: dict[type, type] = {
+ ctypes.c_bool: bool,
+ ctypes.c_byte: np.int8,
+ ctypes.c_double: np.float64,
+ ctypes.c_float: np.float32,
+ ctypes.c_long: np.int32,
+ ctypes.c_short: np.int16,
+ ctypes.c_ubyte: np.uint8,
+ ctypes.c_ulong: np.uint32,
+ ctypes.c_ushort: np.uint16,
+}
+
+# fmt: off
+TYPEDICT: dict[type, tuple[list[int], type, str]] = {
+ imath.BoolArray: ([], ctypes.c_bool, 'array'),
+ imath.DoubleArray: ([], ctypes.c_double, 'array'),
+ imath.FloatArray: ([], ctypes.c_float, 'array'),
+ imath.IntArray: ([], ctypes.c_long, 'array'),
+ imath.ShortArray: ([], ctypes.c_short, 'array'),
+ imath.SignedCharArray: ([], ctypes.c_byte, 'array'),
+ imath.UnsignedCharArray: ([], ctypes.c_ubyte, 'array'),
+ imath.UnsignedIntArray: ([], ctypes.c_ulong, 'array'),
+ imath.UnsignedShortArray: ([], ctypes.c_ushort, 'array'),
+
+ imath.Box2dArray: ([2, 2], ctypes.c_double, 'array'),
+ imath.Box2fArray: ([2, 2], ctypes.c_float, 'array'),
+ imath.Box2iArray: ([2, 2], ctypes.c_long, 'array'),
+ imath.Box2sArray: ([2, 2], ctypes.c_short, 'array'),
+ imath.Box3dArray: ([2, 3], ctypes.c_double, 'array'),
+ imath.Box3fArray: ([2, 3], ctypes.c_float, 'array'),
+ imath.Box3iArray: ([2, 3], ctypes.c_long, 'array'),
+ imath.Box3sArray: ([2, 3], ctypes.c_short, 'array'),
+ imath.C3cArray: ([3], ctypes.c_byte, 'array'),
+ imath.C3fArray: ([3], ctypes.c_float, 'array'),
+ imath.C4cArray: ([4], ctypes.c_byte, 'array'),
+ imath.C4fArray: ([4], ctypes.c_float, 'array'),
+ imath.M22dArray: ([2, 2], ctypes.c_double, 'array'),
+ imath.M22fArray: ([2, 2], ctypes.c_float, 'array'),
+ imath.M33dArray: ([3, 3], ctypes.c_double, 'array'),
+ imath.M33fArray: ([3, 3], ctypes.c_float, 'array'),
+ imath.M44dArray: ([4, 4], ctypes.c_double, 'array'),
+ imath.M44fArray: ([4, 4], ctypes.c_float, 'array'),
+ imath.QuatdArray: ([4], ctypes.c_double, 'array'),
+ imath.QuatfArray: ([4], ctypes.c_float, 'array'),
+ imath.V2dArray: ([2], ctypes.c_double, 'array'),
+ imath.V2fArray: ([2], ctypes.c_float, 'array'),
+ imath.V2iArray: ([2], ctypes.c_long, 'array'),
+ imath.V2sArray: ([2], ctypes.c_short, 'array'),
+ imath.V3dArray: ([3], ctypes.c_double, 'array'),
+ imath.V3fArray: ([3], ctypes.c_float, 'array'),
+ imath.V3iArray: ([3], ctypes.c_long, 'array'),
+ imath.V3sArray: ([3], ctypes.c_short, 'array'),
+ imath.V4dArray: ([4], ctypes.c_double, 'array'),
+ imath.V4fArray: ([4], ctypes.c_float, 'array'),
+ imath.V4iArray: ([4], ctypes.c_long, 'array'),
+ imath.V4sArray: ([4], ctypes.c_short, 'array'),
+
+ imath.Color4cArray2D: ([4], ctypes.c_byte, 'array2d'),
+ imath.Color4fArray2D: ([4], ctypes.c_float, 'array2d'),
+ imath.DoubleArray2D: ([], ctypes.c_double, 'array2d'),
+ imath.FloatArray2D: ([], ctypes.c_float, 'array2d'),
+ imath.IntArray2D: ([], ctypes.c_long, 'array2d'),
+
+ imath.DoubleMatrix: ([], ctypes.c_double, 'matrix'),
+ imath.FloatMatrix: ([], ctypes.c_float, 'matrix'),
+ imath.IntMatrix: ([], ctypes.c_long, 'matrix'),
+
+ imath.Box2d: ([2, 2], ctypes.c_double, 'box'),
+ imath.Box2f: ([2, 2], ctypes.c_float, 'box'),
+ imath.Box2i: ([2, 2], ctypes.c_long, 'box'),
+ imath.Box2s: ([2, 2], ctypes.c_short, 'box'),
+ imath.Box3d: ([2, 3], ctypes.c_double, 'box'),
+ imath.Box3f: ([2, 3], ctypes.c_float, 'box'),
+ imath.Box3i: ([2, 3], ctypes.c_long, 'box'),
+ imath.Box3s: ([2, 3], ctypes.c_short, 'box'),
+
+ imath.Line3d: ([2, 3], ctypes.c_double, 'line'),
+ imath.Line3f: ([2, 3], ctypes.c_float, 'line'),
+
+ imath.Color3c: ([3], ctypes.c_byte, ''),
+ imath.Color3f: ([3], ctypes.c_float, ''),
+ imath.Color4c: ([4], ctypes.c_byte, ''),
+ imath.Color4f: ([4], ctypes.c_float, ''),
+ imath.M22d: ([2, 2], ctypes.c_double, ''),
+ imath.M22dRow: ([2], ctypes.c_double, 'row'),
+ imath.M22f: ([2, 2], ctypes.c_float, ''),
+ imath.M22fRow: ([2], ctypes.c_float, 'row'),
+ imath.M33d: ([3, 3], ctypes.c_double, ''),
+ imath.M33dRow: ([3], ctypes.c_double, 'row'),
+ imath.M33f: ([3, 3], ctypes.c_float, ''),
+ imath.M33fRow: ([3], ctypes.c_float, 'row'),
+ imath.M44d: ([4, 4], ctypes.c_double, ''),
+ imath.M44dRow: ([4], ctypes.c_double, 'row'),
+ imath.M44f: ([4, 4], ctypes.c_float, ''),
+ imath.M44fRow: ([4], ctypes.c_float, 'row'),
+ imath.Quatd: ([4], ctypes.c_double, ''),
+ imath.Quatf: ([4], ctypes.c_float, ''),
+ imath.Shear6d: ([6], ctypes.c_double, ''),
+ imath.Shear6f: ([6], ctypes.c_float, ''),
+ imath.V2d: ([2], ctypes.c_double, ''),
+ imath.V2f: ([2], ctypes.c_float, ''),
+ imath.V2i: ([2], ctypes.c_long, ''),
+ imath.V2s: ([2], ctypes.c_short, ''),
+ imath.V3c: ([3], ctypes.c_byte, ''),
+ imath.V3d: ([3], ctypes.c_double, ''),
+ imath.V3f: ([3], ctypes.c_float, ''),
+ imath.V3i: ([3], ctypes.c_long, ''),
+ imath.V3s: ([3], ctypes.c_short, ''),
+ imath.V4c: ([4], ctypes.c_byte, ''),
+ imath.V4d: ([4], ctypes.c_double, ''),
+ imath.V4f: ([4], ctypes.c_float, ''),
+ imath.V4i: ([4], ctypes.c_long, ''),
+ imath.V4s: ([4], ctypes.c_short, ''),
+}
+# fmt: on
+
+# Define the in-memory structures of the python objects
+# I'm *GUESSING* on most of the types here, so they could be refined
+# in the future if required
+
+# Here's hoping these structs don't change with different versions
+# of python or imath
+
+
+class PyImoObj(ctypes.Structure):
+ _fields_ = [
+ ("refcount", ctypes.c_ssize_t), # from the PyObject c struct
+ ("typeptr", ctypes.c_void_p), # from the PyObject c struct
+ ("unknown1", ctypes.c_ssize_t), # Seems always -48 for some reason
+ ("unknown2", ctypes.c_ssize_t), # Seems always 0
+ ("unknown3", ctypes.c_ssize_t), # Seems always 0
+ ("dataptr", ctypes.c_void_p), # pointer to the PyImoDataObj
+ ]
+
+
+class PyImoDataObj(ctypes.Structure):
+ _fields_ = [
+ ("magic", ctypes.c_ssize_t), # Some kind of type ID?
+ ("unknown1", ctypes.c_ssize_t), # Seems always 0
+ ("dataptr", ctypes.c_void_p), # Pointer to the allocated memory
+ # There's MORE data after this, like the row/column count
+ # and some other pointers. But I don't need them
+ # Plus, there are some edge cases with the different types
+ # like rows or bounding boxes
+ ]
+
+
+PyImoObjPtr = ctypes.POINTER(PyImoObj)
+PyImoDataObjPtr = ctypes.POINTER(PyImoDataObj)
+
+
+def _getImoPointer(imo, extra: str) -> int:
+ """Get the memory address to the actual imath data
+
+ Args:
+ imo (imath object): The imath object to inspect
+ extra (str): The metadata of this current type
+
+ Returns:
+ int: The memory address to the actual data
+ """
+ # This is a scary function
+ # I found this stuff out by trial and error
+ pyStruct = ctypes.cast(id(imo), PyImoObjPtr).contents
+ if extra == "box":
+ # I'm guessing that since the bounding box data is a known
+ # size, they just put it directly into the structure
+ # And the data is stored where the dataptr would be.
+ # So I can just add the pyStruct.dataptr and the memory offset
+ # of the PyImoDataObj.dataptr to get memory address of the box
+ return pyStruct.dataptr + PyImoDataObj.dataptr.offset
+
+ pyImoStruct = ctypes.cast(pyStruct.dataptr, PyImoDataObjPtr).contents
+ return pyImoStruct.dataptr
+
+
+def _link(imo) -> tuple[np.ndarray, int]:
+ """Build a numpy object that's referencing the same memory
+ as the given imath object
+
+ Args:
+ imo (imath object): The imath object to build a link to
+
+ Returns:
+ np.ndarray: The numpy array
+ int: The pointer to the memory address where the data lives
+ """
+ size, cdata, extra = TYPEDICT[type(imo)]
+ if extra == "array":
+ shape = [len(imo)] + size
+ elif extra == "array2d":
+ shape = list(imo.size()) + size
+ elif extra == "matrix":
+ shape = [imo.rows(), imo.columns()] + size
+ elif extra in ("box", "line"):
+ shape = size
+ else:
+ shape = [len(imo)]
+
+ for s in shape[::-1]:
+ cdata = cdata * s # type: ignore
+
+ ptr = _getImoPointer(imo, extra)
+ ctypearray = cdata.from_address(ptr)
+ nparray = np.ctypeslib.as_array(ctypearray)
+ return nparray, ptr
+
+
+def imathToNumpy(imo) -> np.ndarray:
+ """Copy an imath object into a numpy array
+
+ Args:
+ imo (imath object): The imath object to convert
+
+ Returns:
+ np.ndarray: A copy of the imath object as a numpy array
+ """
+ gcarray, _ptr = _link(imo)
+ return np.copy(gcarray)
+
+
+T = TypeVar("T")
+
+
+def numpyToImath(npo: np.ndarray, imtype: Type[T]) -> T:
+ """Convert a numpy array to the given imath type
+
+ Args:
+ npo (array like): An object that can be cast to a numpy array
+ imtype (type): The imath type to convert to
+
+ Returns:
+ imathObj: An instantiated imtype object with the numpy data loaded into it
+ """
+ size, cdata, extra = TYPEDICT[imtype]
+ npo = np.asarray(npo, dtype=NTYPEDICT[cdata])
+ if extra == "array":
+ assert npo.ndim == len(size) + 1
+ imo = imtype(len(npo)) # type: ignore
+ elif extra in ("matrix", "array2d"):
+ assert npo.ndim == 2 + len(size)
+ imo = imtype(*npo.shape[:2])
+ else:
+ imo = imtype()
+
+ tret, _ptr = _link(imo)
+ np.copyto(tret, npo)
+ return imo
+
+
+def _test():
+ """A quick test for all this crazy stuff"""
+
+ # By default the bounding box objects are set to
+ # The min/max for their types
+ box3dnp = np.empty((2, 3), dtype=np.float64)
+ box3dnp[0] = imath.DBL_MAX
+ box3dnp[1] = imath.DBL_MIN
+
+ box3fnp = np.empty((2, 3), dtype=np.float32)
+ box3fnp[0] = imath.FLT_MAX
+ box3fnp[1] = imath.FLT_MIN
+
+ box3fanp = np.empty((5, 2, 3), dtype=np.float32)
+ box3fanp[:] = box3fnp
+
+ # M44fArray
+ eyes = np.zeros((13, 4, 4), dtype=np.float32)
+ eyes[:] = np.eye(4, dtype=np.float32)
+
+ # Line3f
+ line = np.zeros((2, 3), dtype=np.float32)
+ line[1, 0] = 1.0
+
+ # M33fRow
+ im = imath.M33f()
+ nm = np.eye(3, dtype=np.float32)
+
+ # equivalent to an np.empty(3, 5)
+ # so I have to set the values manually
+ dubm = imath.DoubleMatrix(3, 5)
+ tt = 0.0
+ for i in range(3):
+ row = dubm[i]
+ for j in range(5):
+ row[j] = tt
+ tt += 1.0
+
+ # fmt: off
+ eqpairs = [
+ (imath.V3dArray(11), np.zeros((11, 3))),
+ (imath.M44fArray(13), eyes),
+ (imath.V3d(), np.zeros(3)),
+ (imath.Color4cArray2D(5, 7), np.zeros((5, 7, 4), dtype=np.int8)),
+ (imath.Box3fArray(5), box3fanp),
+ (imath.Box3d(), box3dnp),
+ (imath.Box3f(), box3fnp),
+ (imath.FloatArray(13), np.zeros((13), dtype=np.float32)),
+ (imath.Line3f(), line),
+ (im[1], nm[1]),
+ (dubm, np.arange(15, dtype=float).reshape((3, 5))),
+ ]
+ # fmt: on
+
+ for imo, chk in eqpairs:
+ nv = imathToNumpy(imo)
+ assert nv.dtype == chk.dtype
+ assert np.all(nv == chk)
+
+ imtype = type(imo)
+ _size, _cdata, extra = TYPEDICT[imtype]
+ if extra != "row": # Can't directly build row objects
+ _iv = numpyToImath(chk, imtype)
diff --git a/src/python/simplexui/commands/poseblendlib.py b/src/python/simplexui/commands/poseblendlib.py
new file mode 100644
index 00000000..c23b9263
--- /dev/null
+++ b/src/python/simplexui/commands/poseblendlib.py
@@ -0,0 +1,166 @@
+import numpy as np
+
+# I could have used the Scipy rotations library, but it doeosn't deal
+# with ndim arrays of quaternions.
+
+
+def positive_scalar(q: np.ndarray) -> np.ndarray:
+ """Ensure the scalar value of an array of quaternions is positive"""
+ shape = q.shape
+ q = q.reshape((-1, 4))
+ q[q[:, 3] < 0] *= -1
+ return q.reshape(shape)
+
+
+def outer(q: np.ndarray) -> np.ndarray:
+ """Do the outer product of an array of quaternions with itself"""
+ return q[..., None] * q[..., None, :] # broadcast to do the outer products
+
+
+def inverse_quats(q: np.ndarray) -> np.ndarray:
+ """Get the inverses of the given unit quaternions"""
+ ret = q.copy()
+ ret[..., :-1] *= -1
+ return ret
+
+
+def norm_quats(q: np.ndarray) -> np.ndarray:
+ """Normalize the given quaternions and flip any whose scalar component
+ is negative
+ """
+ return positive_scalar(q / np.linalg.norm(q, axis=-1, keepdims=True))
+
+
+def mul_quats(q1, q2):
+ """Quaternion multiplication of two arrays
+
+ Follows the convention that the right hand is applied "first"
+ Like row-major transformation matrices
+ """
+ x1, y1, z1, w1 = q1[..., 0], q1[..., 1], q1[..., 2], q1[..., 3]
+ x2, y2, z2, w2 = q2[..., 0], q2[..., 1], q2[..., 2], q2[..., 3]
+
+ w = w2 * w1 - x2 * x1 - y2 * y1 - z2 * z1
+ x = w2 * x1 + x2 * w1 + y2 * z1 - z2 * y1
+ y = w2 * y1 - x2 * z1 + y2 * w1 + z2 * x1
+ z = w2 * z1 + x2 * y1 - y2 * x1 + z2 * w1
+
+ return np.stack((x, y, z, w), axis=-1)
+
+
+def quaternion_pow(q: np.ndarray, n: np.ndarray):
+ """Exponentiate unit quaternions
+ Could also be thought of as slerping from the unit quaternion to the
+ given quaternions
+
+ Args:
+ q (np.array[..., 4]): An array of quaternions
+ n (np.array[..., 0]): An array of weights broadcastable to the shape
+ of the quaternion array
+
+ Returns:
+ np.array[..., 4]: An array of exponentiated quaternions
+ """
+ w = np.clip(q[..., -1], -1.0, 1.0)
+ v = q[..., :-1]
+
+ theta = np.arccos(w)
+ sin_theta = np.sin(theta)
+
+ # Broadcast n to shape of theta
+ n = np.broadcast_to(n, theta.shape)
+
+ # Scale the rotation angle
+ new_theta = n * theta
+ new_w = np.cos(new_theta)
+
+ # Fall back to a linear approximation for small angles
+ with np.errstate(divide="ignore", invalid="ignore"):
+ scale = np.where(sin_theta > 1e-8, np.sin(new_theta) / sin_theta, n)
+
+ new_v = v * scale[..., np.newaxis]
+
+ result = np.concatenate([new_v, new_w[..., np.newaxis]], axis=-1)
+ return result
+
+
+def sum_weighted_quat_poses(
+ restPose: np.ndarray, # (N, 4) float
+ targets: np.ndarray, # (T, N, 4) float
+ weights: np.ndarray, # T float
+ levels: np.ndarray, # T int
+) -> np.ndarray: # (N, 4) float
+ """Perform a "weighted sum" of target poses of quaternions with multiple
+ levels. Each level is calculated individually, then multiplied in order
+ to get the final output.
+
+ This assumes Scalar (W) Last formatted quaternions
+
+ This uses some interesting math that I don't fully understand. But the idea
+ came from a NASA paper on the topic.
+
+ Apparently if you sum a bunch of the outer products of quaternions with
+ themselves, and then take the largest eigenvector of the resulting 4x4
+ matrix, that eigenvector is (in some sense) the average of those quaternions.
+
+ The naiive idea of an average is the sum divided by the count. So if you
+ multiply an average value by the count, you should get back the sum.
+
+ So if I take this "average" of quaternions and do a "multiplication" by the
+ count, I should get back something that looks like a "sum" of the quaternions
+
+ And to do "multiplication by the count", I just extrapolate the quaternion
+ by slerping it to that count.
+
+ This may not be "correct", but it sure seems to behave like I want it to.
+ And that's all that matters for this application.
+
+ Args:
+ restPose (np.array[N, 4]): An array of quaternions
+ targets (np.array[T, N, 4]): An array of target poses of quaternions
+ weights (np.array[T]): The weight of each target
+ levels (np.array[T]): The level of each target
+
+ Returns:
+ np.array[N, 4]: An array of quaternions
+ """
+ targets = norm_quats(targets)
+ restPose = norm_quats(restPose)
+
+ # Get the existing levels, and how many poses are at each level
+ exlev, counts = np.unique(levels, return_counts=True)
+ s_exlev = np.argsort(exlev)
+ exlev = exlev[s_exlev]
+ counts = counts[s_exlev]
+
+ # Get the targets in the space of the rest pose
+ restInv = inverse_quats(restPose)
+ relTargets = mul_quats(targets, restInv[None])
+
+ # Apply the weights to all the targets
+ slerped = quaternion_pow(relTargets, weights[..., None])
+
+ # Get the outer product of all the quaternions
+ # and sum them by level
+ outs = outer(slerped)
+ levelEigs = np.zeros((exlev[-1] + 1, len(restPose), 4, 4))
+ np.add.at(levelEigs, levels, outs)
+ levelEigs = levelEigs[exlev]
+
+ # Get largest eigenvalue for each matrix
+ # This represents the *average* of each level
+ evals, evecs = np.linalg.eig(levelEigs)
+ mvals = np.argmax(np.abs(evals), axis=-1)[..., None, None]
+ averages = np.take_along_axis(evecs, mvals, axis=evecs.ndim - 1).squeeze(axis=-1)
+ averages = norm_quats(averages)
+
+ # Take each level's pose to the power of its count
+ sums = quaternion_pow(averages, counts[:, None])
+ sums = norm_quats(sums)
+
+ # Finally multiply the poses together on top of the rest pose
+ qret = restPose.copy()
+ for x in sums:
+ qret = mul_quats(x, qret)
+
+ return qret
diff --git a/scripts/simplexui/commands/reorderSimplexPoints.py b/src/python/simplexui/commands/reorderSimplexPoints.py
similarity index 93%
rename from scripts/simplexui/commands/reorderSimplexPoints.py
rename to src/python/simplexui/commands/reorderSimplexPoints.py
index 0393e39f..82ca7eef 100644
--- a/scripts/simplexui/commands/reorderSimplexPoints.py
+++ b/src/python/simplexui/commands/reorderSimplexPoints.py
@@ -15,7 +15,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-""" Transfer shapes between mismatched models
+"""Transfer shapes between mismatched models
Given a 1:1 point correspondence, transfer the shapes from one
geometry to another. Figuring out the correspondence is currently
@@ -25,9 +25,8 @@
will be used as a numpy index to get the output values. It's also
possible to invert the range if you think you've got it backwards
"""
-# pylint:disable=wrong-import-position
-from __future__ import absolute_import, print_function
+# pylint:disable=wrong-import-position
import json
from .alembicCommon import buildSmpx, readSmpx
diff --git a/scripts/simplexui/commands/rigidAlign.py b/src/python/simplexui/commands/rigidAlign.py
similarity index 94%
rename from scripts/simplexui/commands/rigidAlign.py
rename to src/python/simplexui/commands/rigidAlign.py
index 43d036a9..e51acb6a 100644
--- a/scripts/simplexui/commands/rigidAlign.py
+++ b/src/python/simplexui/commands/rigidAlign.py
@@ -15,10 +15,6 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import absolute_import
-
-from six.moves import range
-
try:
import numpy as np
except ImportError:
diff --git a/scripts/simplexui/commands/smpxBlend.py b/src/python/simplexui/commands/smpxBlend.py
similarity index 94%
rename from scripts/simplexui/commands/smpxBlend.py
rename to src/python/simplexui/commands/smpxBlend.py
index 80f9e328..d8c1b772 100644
--- a/scripts/simplexui/commands/smpxBlend.py
+++ b/src/python/simplexui/commands/smpxBlend.py
@@ -15,11 +15,6 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import print_function
-
-import six
-from six.moves import zip
-
from ..items import (
Combo,
ComboPair,
@@ -72,8 +67,8 @@ def smpxMismatchCheck(simpA, simpB):
saSliderNames = {sa.name: sa for sa in simpA.sliders}
sbSliderNames = {sb.name: sb for sb in simpB.sliders}
- saSliderNameUnique = six.viewkeys(saSliderNames) - six.viewkeys(sbSliderNames)
- sbSliderNameUnique = six.viewkeys(sbSliderNames) - six.viewkeys(saSliderNames)
+ saSliderNameUnique = saSliderNames.keys() - sbSliderNames.keys()
+ sbSliderNameUnique = sbSliderNames.keys() - saSliderNames.keys()
slnMatch = {}
for san in saSliderNameUnique:
slnMatch[san] = (san, None)
@@ -83,8 +78,8 @@ def smpxMismatchCheck(simpA, simpB):
saComboNames = {sa.name: sa for sa in simpA.combos}
sbComboNames = {sb.name: sb for sb in simpB.combos}
- saComboNameUnique = six.viewkeys(saComboNames) - six.viewkeys(sbComboNames)
- sbComboNameUnique = six.viewkeys(sbComboNames) - six.viewkeys(saComboNames)
+ saComboNameUnique = saComboNames.keys() - sbComboNames.keys()
+ sbComboNameUnique = sbComboNames.keys() - saComboNames.keys()
# TODO search for combos with mis-ordered inputs
cnMatch = {}
for can in saComboNameUnique:
@@ -95,8 +90,8 @@ def smpxMismatchCheck(simpA, simpB):
saTravNames = {sa.name: sa for sa in simpA.traversals}
sbTravNames = {sb.name: sb for sb in simpB.traversals}
- saTravNameUnique = six.viewkeys(saTravNames) - six.viewkeys(sbTravNames)
- sbTravNameUnique = six.viewkeys(sbTravNames) - six.viewkeys(saTravNames)
+ saTravNameUnique = saTravNames.keys() - sbTravNames.keys()
+ sbTravNameUnique = sbTravNames.keys() - saTravNames.keys()
# TODO search for travs with mis-ordered inputs
tnMatch = {}
for tan in saTravNameUnique:
diff --git a/scripts/simplexui/commands/unsubdivide.py b/src/python/simplexui/commands/unsubdivide.py
similarity index 94%
rename from scripts/simplexui/commands/unsubdivide.py
rename to src/python/simplexui/commands/unsubdivide.py
index 06d42453..26178ae7 100644
--- a/scripts/simplexui/commands/unsubdivide.py
+++ b/src/python/simplexui/commands/unsubdivide.py
@@ -18,13 +18,8 @@
# pylint: disable=unused-argument, too-many-locals
# pylint:disable=E0611,E0401
-from __future__ import absolute_import, print_function
-
import json
-from itertools import chain
-
-import six
-from six.moves import map, range, zip, zip_longest
+from itertools import chain, zip_longest
from Qt.QtWidgets import QApplication
from .alembicCommon import buildSmpx, pbPrint, readSmpx
@@ -64,11 +59,11 @@ def mergeCycles(groups):
heads = {g[0]: g for g in groups}
tails = {g[-1]: g for g in groups}
- headGetter = lambda x: heads.get(x[-1])
- headSetter = lambda x, y: x + y[1:]
+ headGetter = lambda x: heads.get(x[-1]) # noqa: E731
+ headSetter = lambda x, y: x + y[1:] # noqa: E731
- tailGetter = lambda x: tails.get(x[0])
- tailSetter = lambda x, y: y + x[1:]
+ tailGetter = lambda x: tails.get(x[0]) # noqa: E731
+ tailSetter = lambda x, y: y + x[1:] # noqa: E731
searches = ((headGetter, headSetter), (tailGetter, tailSetter))
@@ -155,7 +150,7 @@ def buildHint(island, neigh, borders):
d.setdefault(len(neigh[v]), []).append(v)
dd = {}
- for k, v in six.iteritems(d):
+ for k, v in d.items():
dd.setdefault(len(v), []).append(k)
mkey = min(dd.keys())
@@ -198,7 +193,7 @@ def partitionIslands(faces, neigh, pBar=None):
QApplication.processEvents()
while allVerts:
- verts = set([allVerts.pop()])
+ verts = {allVerts.pop()}
exclude = set()
while verts:
verts, exclude = grow(neigh, verts, exclude)
@@ -406,7 +401,7 @@ def buildNeighborDict(faces):
borders = set()
out = {}
- for k, v in six.iteritems(fanDict):
+ for k, v in fanDict.items():
fans, cycles = mergeCycles(v)
for f, c in zip(fans, cycles):
if not c:
@@ -462,7 +457,7 @@ def buildLayeredNeighborDicts(faces, uFaces, dWings):
borders >= uBorders
), "Somehow the unsubdivided borders contain different vIdxs"
- for i, (k, uNeigh) in enumerate(six.iteritems(uNeighDict)):
+ for k, uNeigh in uNeighDict.items():
neighDict[k] = _align(neighDict[k], uNeigh, dWings)
return neighDict, uNeighDict, edgeDict, uEdgeDict, borders
@@ -601,13 +596,13 @@ def _findOldPosition3Valence(
for x, v in enumerate(fNeigh):
# working with neigh, but should only ever contain uNeigh indexes
eTest = edgeDict[vIdx]
- origFace = set([n for n in neighDict[v][0] if n not in eTest])
+ origFace = {n for n in neighDict[v][0] if n not in eTest}
- check = (origFace - set(ueNeigh)) - set([vIdx])
+ check = (origFace - set(ueNeigh)) - {vIdx}
if computed >= check:
fCtrIdx = v
fnIdx = x
- fik = sorted(list(check))
+ fik = sorted(check)
break
if fnIdx is None:
@@ -690,7 +685,7 @@ def deleteCenters(meshFaces, uvFaces, centerDel, pBar=None):
pBar.setMaximum(len(faceDelDict))
chk = -1
- for idx, rFaces in six.iteritems(faceDelDict):
+ for idx, rFaces in faceDelDict.items():
chk += 1
if pBar is not None:
pBar.setValue(chk)
@@ -784,7 +779,7 @@ def fixVerts(
An array of vertex positions
"""
uVerts = verts.copy()
- uIdxs = sorted(list(set([i for i in chain.from_iterable(uFaces)])))
+ uIdxs = sorted(set(chain.from_iterable(uFaces)))
v3Idxs = []
# bowtie verts are pinned
@@ -938,13 +933,13 @@ def collapse(faces, verts, uvFaces, uvs):
: np.array
The new N*2 array of uvs
"""
- vset = sorted(list(set(chain.from_iterable(faces))))
+ vset = sorted(set(chain.from_iterable(faces)))
nVerts = verts[vset]
vDict = {v: i for i, v in enumerate(vset)}
nFaces = [[vDict[f] for f in face] for face in faces]
if uvFaces is not None:
- uvset = sorted(list(set(chain.from_iterable(uvFaces))))
+ uvset = sorted(set(chain.from_iterable(uvFaces)))
nUVs = uvs[uvset]
uvDict = {v: i for i, v in enumerate(uvset)}
nUVFaces = [[uvDict[f] for f in face] for face in uvFaces]
diff --git a/scripts/simplexui/commands/uvTransfer.py b/src/python/simplexui/commands/uvTransfer.py
similarity index 95%
rename from scripts/simplexui/commands/uvTransfer.py
rename to src/python/simplexui/commands/uvTransfer.py
index eb5b807d..ff69dfbd 100644
--- a/scripts/simplexui/commands/uvTransfer.py
+++ b/src/python/simplexui/commands/uvTransfer.py
@@ -16,16 +16,7 @@
# along with Simplex. If not, see .
# pylint: disable=invalid-name
-from __future__ import absolute_import, division, print_function
-
-import six
-from six.moves import range, zip
-
-try:
- import numpy as np
-except ImportError:
- pass
-
+import numpy as np
INF = float("inf")
EPS = 1e-7
@@ -213,7 +204,7 @@ def mmvc(rawFaces, points, samples, uvToFace, tol=EPS):
barys[idxs] = b
ret = {}
- for qIdxs, barys in six.itervalues(out):
+ for qIdxs, barys in out.values():
for qi, b in zip(qIdxs, barys):
ret[qi] = (uvToFace[qi], b)
@@ -558,7 +549,7 @@ def triangulateUVs(faces, uvs):
negArea = np.where(signedAreas < 0)
# Do we need to retriangulate any of the polys
- retri = sorted(set(triMap[i] for i in negArea[0]))
+ retri = sorted({triMap[i] for i in negArea[0]})
if retri:
tris = tris.tolist()
tmpFaceMap = {}
@@ -652,7 +643,7 @@ def getUvCorrelation(samples, points, faces, tol=0.0001, handleMissing=True, pBa
"""
tris, triMap, borderMap = triangulateUVs(faces, points)
swept, missing = sweep(samples, points, tris, pBar=pBar)
- uvToFace = {uvI: triMap[tI] for uvI, tI in six.iteritems(swept)}
+ uvToFace = {uvI: triMap[tI] for uvI, tI in swept.items()}
# import __main__
# __main__.__dict__.update(locals())
# raise RuntimeError("STOPPIT")
@@ -760,7 +751,7 @@ def getVertCorrelation(
continue
mvcVertDict.setdefault(childUvToVert[uvIdx], []).append(mvcUvDict[uvIdx])
- missing = set(range(cNumVerts)) - six.viewkeys(mvcVertDict)
+ missing = set(range(cNumVerts)) - mvcVertDict.keys()
if missing:
import time
@@ -799,7 +790,7 @@ def applyTransfer(parVerts, parFaces, correlation, outputSize):
parVerts = parVerts[None, ...]
rows, cols, vals = [], [], []
- for cVertIdx, corrPoss in six.iteritems(correlation):
+ for cVertIdx, corrPoss in correlation.items():
if len(corrPoss) == 1:
pFaceIdx, bary = corrPoss[0]
else:
diff --git a/scripts/simplexui/dragFilter.py b/src/python/simplexui/dragFilter.py
similarity index 89%
rename from scripts/simplexui/dragFilter.py
rename to src/python/simplexui/dragFilter.py
index 5b208a0c..f53c4fdb 100644
--- a/scripts/simplexui/dragFilter.py
+++ b/src/python/simplexui/dragFilter.py
@@ -51,13 +51,13 @@ class DragFilter(QObject):
The cursor that will be displayed while dragging
CURSOR_ARROWS will show horizontal/vertical drag
CURSOR_BLANK will hid the cursor
- dragButton(Qt.MouseButton): default=Qt.MiddleButton
+ dragButton(Qt.MouseButton): default=Qt.MouseButton.MiddleButton
The button that will kick off the drag behavior
- fastModifier(Qt.KeyboardModifier): default=Qt.ControlModifier
+ fastModifier(Qt.KeyboardModifier): default=Qt.KeyboardModifier.ControlModifier
The modifier key that will cause the multiplier to emit with the signal
fastMultiplier(float): default=5.0
The size of the multiplier
- slowModifier(Qt.KeyboardModifier): default=Qt.ShiftModifier
+ slowModifier(Qt.KeyboardModifier): default=Qt.KeyboardModifier.ShiftModifier
The modifier key that will cause the divisor to emit with the signal
slowDivisor(float): default=5.0
The size of the divisor
@@ -88,15 +88,15 @@ def __init__(self, parent):
self.cursorLock = False
self.wrapBoundary = 10 # wrap when within boundary of screen edge
self.dragCursor = self.CURSOR_ARROWS
- self.dragButton = Qt.MiddleButton
+ self.dragButton = Qt.MouseButton.MiddleButton
# The QSpinbox has an option where, if you hold down the mouse button
# it will continually increment. This flag enables a workaround
# for that problem
self.isSpinbox = False
- self.fastModifier = Qt.ControlModifier
- self.slowModifier = Qt.ShiftModifier
+ self.fastModifier = Qt.KeyboardModifier.ControlModifier
+ self.slowModifier = Qt.KeyboardModifier.ShiftModifier
self.fastMultiplier = 5.0
self.slowDivisor = 5.0
@@ -116,12 +116,12 @@ def doOverrideCursor(self):
if self._overridden:
return
if self.dragCursor == self.CURSOR_BLANK:
- QApplication.setOverrideCursor(Qt.BlankCursor)
+ QApplication.setOverrideCursor(Qt.CursorShape.BlankCursor)
elif self.dragCursor == self.CURSOR_ARROWS:
if self._dragType == self.DRAG_VERTICAL:
- QApplication.setOverrideCursor(Qt.SizeVerCursor)
+ QApplication.setOverrideCursor(Qt.CursorShape.SizeVerCursor)
elif self._dragType == self.DRAG_HORIZONTAL:
- QApplication.setOverrideCursor(Qt.SizeHorCursor)
+ QApplication.setOverrideCursor(Qt.CursorShape.SizeHorCursor)
self._overridden = True
@@ -228,7 +228,7 @@ def startDrag(self, o, e):
# otherwise the spinbox will keep ticking. @longClickFix
# There's gotta be a better way to do this :-/
mouseup = QMouseEvent(
- QEvent.MouseButtonRelease,
+ QEvent.Type.MouseButtonRelease,
e.pos(),
self.dragButton,
e.buttons(),
@@ -270,7 +270,7 @@ def eventFilter(self, o, e):
The QEvent of the mouse drag
"""
if hasattr(self, "DRAG_ENABLED"):
- if e.type() == QEvent.MouseMove:
+ if e.type() == QEvent.Type.MouseMove:
if self._isDragging:
try:
if self._dragType != self.DRAG_NONE:
@@ -283,14 +283,14 @@ def eventFilter(self, o, e):
raise # re-raise the exception
return True
- elif e.type() == QEvent.MouseButtonRelease:
+ elif e.type() == QEvent.Type.MouseButtonRelease:
self.myendDrag(o, e)
if e.button() & self.dragButton:
# Catch any dragbutton releases and handle them
self._isDragging = False
return True
- elif e.type() == QEvent.MouseButtonPress:
+ elif e.type() == QEvent.Type.MouseButtonPress:
if e.button() & self.dragButton:
# Catch any dragbutton presses and handle them
self._isDragging = True
diff --git a/scripts/simplexui/falloffDialog.py b/src/python/simplexui/falloffDialog.py
similarity index 91%
rename from scripts/simplexui/falloffDialog.py
rename to src/python/simplexui/falloffDialog.py
index 308116a7..5a5a53da 100644
--- a/scripts/simplexui/falloffDialog.py
+++ b/src/python/simplexui/falloffDialog.py
@@ -17,14 +17,9 @@
# This module imports QT from PyQt4, PySide or PySide2
# Depending on what's available
-from __future__ import absolute_import, print_function
-
import os
import re
-import six
-from six.moves import range
-
import Qt as QtLib
from .interfaceModel import FalloffDataModel
from .items import Falloff
@@ -84,9 +79,9 @@ def __init__(self, parent):
self.canvasMargin = 16
self.setMinimumHeight(2 * self.canvasMargin)
- self.bgColor = Qt.white
- self.lineColor = Qt.black
- self.limitColor = Qt.gray
+ self.bgColor = Qt.GlobalColor.white
+ self.lineColor = Qt.GlobalColor.black
+ self.limitColor = Qt.GlobalColor.gray
def setTangent(self, leftTan=None, rightTan=None):
"""Set the falloff tangents, clamped 0 to 1
@@ -155,17 +150,17 @@ def _drawCleanLine(self, painter, p1, p2):
def _paintBG(self, painter):
painter.save()
- painter.setBrush(self.palette().color(QPalette.Background))
+ painter.setBrush(self.palette().color(QPalette.ColorRole.Background))
painter.drawRect(0, 0, self.width(), self.height())
painter.restore()
def _paintLimits(self, painter):
painter.save()
# pen = QPen(self.limitColor)
- baseColor = self.palette().color(QPalette.Base)
+ baseColor = self.palette().color(QPalette.ColorRole.Base)
pen = QPen(baseColor)
pen.setWidth(1)
- pen.setStyle(Qt.DashLine)
+ pen.setStyle(Qt.PenStyle.DashLine)
painter.setPen(pen)
self._drawCleanLine(
painter, self.mapToCanvas(QPoint(0, 0)), self.mapToCanvas(QPoint(1, 0))
@@ -181,16 +176,16 @@ def _paintPath(self, painter, p0, p1, p2, p3):
path.moveTo(p0)
path.cubicTo(p1, p2, p3)
# painter.strokePath(path, QPen(QBrush(self.lineColor), 2))
- foregroundColor = self.palette().color(QPalette.Foreground)
+ foregroundColor = self.palette().color(QPalette.ColorRole.Foreground)
painter.strokePath(path, QPen(QBrush(foregroundColor), 2))
painter.restore()
def _paintTangents(self, painter, p0, p1, p2, p3):
# draw the tangent lines
- foregroundColor = self.palette().color(QPalette.Foreground)
+ foregroundColor = self.palette().color(QPalette.ColorRole.Foreground)
pen = QPen(foregroundColor)
pen.setWidth(1)
- pen.setStyle(Qt.DashLine)
+ pen.setStyle(Qt.PenStyle.DashLine)
painter.setPen(pen)
painter.drawLine(p0, p1)
painter.drawLine(p3, p2)
@@ -202,7 +197,7 @@ def _paintTangents(self, painter, p0, p1, p2, p3):
def paintEvent(self, e):
painter = QPainter(self)
- painter.setRenderHint(QPainter.Antialiasing)
+ painter.setRenderHint(QPainter.RenderHint.Antialiasing)
self._paintBG(painter)
self._paintLimits(painter)
@@ -252,7 +247,7 @@ def findControlPoint(self, point, tolerance=10):
return None
def mousePressEvent(self, e):
- if e.button() == Qt.LeftButton:
+ if e.button() == Qt.MouseButton.LeftButton:
self._activeControlPoint = self.findControlPoint(e.pos())
if self._activeControlPoint is not None:
self.mouseMoveEvent(e)
@@ -260,7 +255,7 @@ def mousePressEvent(self, e):
e.accept()
def mouseReleaseEvent(self, e):
- if e.button() == Qt.LeftButton:
+ if e.button() == Qt.MouseButton.LeftButton:
self._activeControlPoint = None
self.mouseDrag = False
e.accept()
@@ -297,7 +292,7 @@ def __init__(self, parent):
self.foModel = QStandardItemModel()
self.uiFalloffWID = CurveEditWidget(self)
- policy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
+ policy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding)
# policy.setVerticalStretch(1)
self.uiFalloffWID.setSizePolicy(policy)
self.uiFalloffWID.tangentUpdated.connect(self.updateTangents)
@@ -328,8 +323,8 @@ def updateTangents(self, leftTangent, rightTangent):
leftTanIdx = self.foModel.index(cbIdx, 5)
rightTanIdx = self.foModel.index(cbIdx, 4)
- self.foModel.setData(leftTanIdx, leftTangent, role=Qt.EditRole)
- self.foModel.setData(rightTanIdx, rightTangent, role=Qt.EditRole)
+ self.foModel.setData(leftTanIdx, leftTangent, role=Qt.ItemDataRole.EditRole)
+ self.foModel.setData(rightTanIdx, rightTangent, role=Qt.ItemDataRole.EditRole)
def setLeftTangent(self, val):
self.uiFalloffWID.setTangent(leftTan=val)
@@ -366,7 +361,7 @@ def loadSimplex(self):
print("Adding Mappings")
currentIndex = "currentIndex"
- if six.PY3 and (QtLib.IsPySide2 or QtLib.IsPyQt5):
+ if QtLib.IsPySide2 or QtLib.IsPyQt5:
currentIndex = QByteArray(bytes("Test", encoding="utf-8"))
self._falloffMapper.addMapping(self.uiFalloffTypeCBOX, 1, currentIndex)
diff --git a/scripts/simplexui/img/ChefHead.png b/src/python/simplexui/img/ChefHead.png
similarity index 100%
rename from scripts/simplexui/img/ChefHead.png
rename to src/python/simplexui/img/ChefHead.png
diff --git a/scripts/simplexui/img/frozen.png b/src/python/simplexui/img/frozen.png
similarity index 100%
rename from scripts/simplexui/img/frozen.png
rename to src/python/simplexui/img/frozen.png
diff --git a/scripts/simplexui/interface/__init__.py b/src/python/simplexui/interface/__init__.py
similarity index 87%
rename from scripts/simplexui/interface/__init__.py
rename to src/python/simplexui/interface/__init__.py
index c142e40d..d577438f 100644
--- a/scripts/simplexui/interface/__init__.py
+++ b/src/python/simplexui/interface/__init__.py
@@ -16,15 +16,13 @@
# along with Simplex. If not, see .
# This file will serve as the only place where the choice of DCC will be chosen
-from __future__ import absolute_import
-
import os
import sys
CONTEXT = os.path.basename(sys.executable)
if CONTEXT in ("maya.exe", "mayabatch.exe", "maya.bin"):
from .mayaInterface import DCC, DISPATCH, rootWindow, undoContext
-elif CONTEXT in ("XSI.exe", "xsi.bin"):
- from .xsiInterface import DCC, DISPATCH, rootWindow, undoContext
else:
from .dummyInterface import DCC, DISPATCH, rootWindow, undoContext
+
+__all__ = ["DCC", "DISPATCH", "rootWindow", "undoContext"]
diff --git a/scripts/simplexui/interface/dummyInterface.py b/src/python/simplexui/interface/dummyInterface.py
similarity index 93%
rename from scripts/simplexui/interface/dummyInterface.py
rename to src/python/simplexui/interface/dummyInterface.py
index a980eafd..3546163c 100644
--- a/scripts/simplexui/interface/dummyInterface.py
+++ b/src/python/simplexui/interface/dummyInterface.py
@@ -16,15 +16,12 @@
# along with Simplex. If not, see .
# pylint: disable=invalid-name, unused-argument
-""" A placeholder interface that takes arguments and does nothing with them """
-from __future__ import absolute_import
+"""A placeholder interface that takes arguments and does nothing with them"""
import copy
from contextlib import contextmanager
from functools import wraps
-from six.moves import map, zip
-
from Qt import QtCore
from Qt.QtCore import Signal
@@ -276,7 +273,7 @@ def postLoad(self, simp, preRet):
pass
def checkForErrors(self, window):
- """ Check for any DCC specific errors
+ """Check for any DCC specific errors
Parameters
----------
@@ -441,6 +438,22 @@ def vertCount(mesh):
"""
return len(mesh.verts)
+ @undoable
+ def loadAbcPoses(self, abcMesh, js, pBar=None):
+ """Load the joints/skin from an alembic file onto an already-created system
+
+ Parameters
+ ----------
+ abcMesh : IPolyMesh
+ The Alembic mesh to load shapes from
+ js : dict
+ The simplex definition dictionary
+ pBar : QProgressDialog, optional
+ An optional progress dialog (Default value = None)
+
+ """
+ pass
+
@undoable
def loadAbc(self, abcMesh, js, pBar=None):
"""Load the shapes from an alembic file onto an already-created system
@@ -475,7 +488,7 @@ def getAllShapeVertices(self, shapes, pBar=None):
An optional progress dialog (Default value = None)
"""
- for i, shape in enumerate(shapes):
+ for shape in shapes:
verts = self.getShapeVertices(shape)
shape.verts = verts
@@ -634,6 +647,14 @@ def exportOtherAbc(self, dccMesh, abcMesh, js, world=False, pBar=None):
self.exportAbc(
dccMesh, abcMesh, js, world=world, ensureCorrect=False, pBar=pBar
)
+
+ def deleteObj(self, dccMesh, path):
+ """Export a mesh to the given path"""
+ pass
+
+ def exportMesh(self, dccMesh, path):
+ """Export a mesh to the given path"""
+ pass
# Revision tracking
def getRevision(self):
@@ -668,7 +689,7 @@ def renameSystem(self, name):
"""
# TODO
- oldName = self.name
+ # oldName = self.name
self.name = name
# for dd in (DB.nodes, DB.ops, DB.bss, DB.meshes):
# oo = dd.get(oldName)
diff --git a/scripts/simplexui/interface/mayaInterface.py b/src/python/simplexui/interface/mayaInterface.py
similarity index 83%
rename from scripts/simplexui/interface/mayaInterface.py
rename to src/python/simplexui/interface/mayaInterface.py
index 010da719..54a01916 100644
--- a/scripts/simplexui/interface/mayaInterface.py
+++ b/src/python/simplexui/interface/mayaInterface.py
@@ -16,22 +16,19 @@
# along with Simplex. If not, see .
# pylint: disable=invalid-name
-from __future__ import absolute_import
-
+from __future__ import annotations
import json
import re
from contextlib import contextmanager
-from ctypes import c_double, c_float
from functools import wraps
+from typing import TYPE_CHECKING
import maya.cmds as cmds
import maya.OpenMaya as om
-import six
from alembic.AbcGeom import GeometryScope, OPolyMeshSchemaSample, OV2fGeomParamSample
from imath import IntArray, UnsignedIntArray, V2fArray, V3fArray
-from six.moves import map, range, zip
-from ..commands.alembicCommon import mkSampleVertexPoints
+from ..commands.alembicCommon import mkSampleVertexPoints, buildAbc
from Qt import QtCore
from Qt.QtCore import Signal
from Qt.QtWidgets import (
@@ -42,15 +39,17 @@
QSplashScreen,
)
+if TYPE_CHECKING:
+ from ..items.simplex import Simplex
+
try:
import numpy as np
+ from ..commands.numpytoimath import numpyToImath
+ from ..commands.mayatonumpy import mayaToNumpy
except ImportError:
np = None
-
-try:
- import imathnumpy
-except ImportError:
- imathnumpy = None
+ numpyToImath = None
+ mayaToNumpy = None
# UNDO STACK INTEGRATION
@@ -117,9 +116,8 @@ def stacker(*args, **kwargs):
return stacker
-# temporarily disconnect inputs from a list of nodes and plugs
def doDisconnect(targets, testCnxType=("double", "float")):
- """
+ """Temporarily disconnect inputs from a list of nodes and plugs
Parameters
----------
@@ -169,8 +167,8 @@ def doReconnect(cnxs):
-------
"""
- for tdict in six.itervalues(cnxs):
- for s, d in six.iteritems(tdict):
+ for tdict in cnxs.values():
+ for s, d in tdict.items():
if not cmds.isConnected(s, d):
cmds.connectAttr(s, d, force=True)
@@ -199,6 +197,14 @@ def disconnected(targets, testCnxType=("double", "float")):
doReconnect(cnxs)
+def split_trailing_digits(s: str) -> tuple[str, str]:
+ """Split trailing digits from a string into (prefix, digits)."""
+ match = re.match(r"^(.*?)(\d+)?$", s)
+ if match:
+ return match.group(1), match.group(2) or ""
+ return s, ""
+
+
class DCC(object):
""" """
@@ -207,15 +213,17 @@ class DCC(object):
def __init__(self, simplex, stack=None):
if not cmds.pluginInfo("simplex_maya", query=True, loaded=True):
cmds.loadPlugin("simplex_maya")
- self.undoDepth = 0
- self.name = None # the name of the system
- self.mesh = None # the mesh object with the system
- self.ctrl = None # the object that has all the controllers on it
- self.shapeNode = None # the deformer object
- self.op = None # the simplex object
- self.simplex = simplex # the abstract representation of the setup
- self._live = True
- self.sliderMul = self.simplex.sliderMul
+ self.undoDepth: int = 0
+ self.name: str = "" # the name of the system
+ self.mesh: str = "" # the mesh object with the system
+ self.ctrl: str = "" # the object that has all the controllers on it
+ self.shapeNode: str = "" # the deformer object
+ self.hasPoseNode = False
+ self.poseNode: str = "" # the pose deformer object
+ self.op: str = "" # the simplex object
+ self.simplex: Simplex = simplex # the abstract representation of the setup
+ self._live: bool = True
+ self.sliderMul: float = self.simplex.sliderMul
# def __deepcopy__(self, memo):
# '''
@@ -274,7 +282,6 @@ def _checkAllShapeValidity(self, shapeNames):
missingNames.append(shapeName)
return missingNames, len(attrs)
-
@classmethod
def _removeExtraShapeNodes(cls, tfm):
shapeNodes = cmds.listRelatives(tfm, shapes=True, noIntermediate=True)
@@ -282,7 +289,7 @@ def _removeExtraShapeNodes(cls, tfm):
keeper = None
todel = []
for sn in shapeNodes:
- tfmChk = ''.join(sn.rsplit('Shape', 1))
+ tfmChk = "".join(sn.rsplit("Shape", 1))
if tfmChk == tfm:
keeper = sn
else:
@@ -290,7 +297,6 @@ def _removeExtraShapeNodes(cls, tfm):
if keeper is not None:
cmds.delete(todel)
-
def preLoad(self, simp, simpDict, create=True, pBar=None):
"""
@@ -326,21 +332,18 @@ def preLoad(self, simp, simpDict, create=True, pBar=None):
toMake, nextIndex = self._checkAllShapeValidity(shapeNames)
if not toMake:
- return
+ return
if not create:
if pBar is not None:
- msg = "\n".join(
- [
- "Some shapes are Missing:",
- ", ".join(toMake),
- "",
- "Create them?",
- ]
+ msg = "Some shapes are Missing:\n{}\n\nCreate them?"
+ msg = msg.format(", ".join(toMake))
+ btns = (
+ QMessageBox.StandardButton.Yes
+ | QMessageBox.StandardButton.Cancel
)
- btns = QMessageBox.Yes | QMessageBox.Cancel
bret = QMessageBox.question(pBar, "Missing Shapes", msg, btns)
- if not bret & QMessageBox.Yes:
+ if not bret & QMessageBox.StandardButton.Yes:
raise RuntimeError("Missing Shapes: {}".format(toMake))
else:
raise RuntimeError("Missing Shapes: {}".format(toMake))
@@ -379,7 +382,6 @@ def preLoad(self, simp, simpDict, create=True, pBar=None):
cmds.undoInfo(state=True)
raise
-
def postLoad(self, simp, preRet):
"""
@@ -396,7 +398,7 @@ def postLoad(self, simp, preRet):
cmds.undoInfo(state=True)
def checkForErrors(self, window):
- """ Check for any DCC specific errors
+ """Check for any DCC specific errors
Parameters
----------
@@ -408,54 +410,33 @@ def checkForErrors(self, window):
msg = (
"The current mesh has multiple shape nodes.",
"The UI will still mostly work, but extracting/connecting shapes"
- "may fail in unexpected ways."
+ "may fail in unexpected ways.",
)
- QMessageBox.warning(window, "Multiple Shape Nodes", '\n'.join(msg))
+ QMessageBox.warning(window, "Multiple Shape Nodes", "\n".join(msg))
- # System IO
- @undoable
- def loadNodes(self, simp, thing, create=True, pBar=None):
- """Create a new system based on the simplex tree
- Build any DCC objects that are missing if create=True
- Raises a runtime error if missing objects are found and
- create=False
-
- Parameters
- ----------
- simp :
-
- thing :
-
- create :
- (Default value = True)
- pBar :
- (Default value = None)
-
- Returns
- -------
-
- """
- self.name = simp.name
- self.mesh = thing
-
- # Find all blendshapes in the history
- rawShapeNodes = [
- h for h in cmds.listHistory(thing) if cmds.nodeType(h) == "blendShape"
- ]
+ def _getOpNodes(self, thing: str):
+ hist: list[str] = cmds.listHistory(thing)
+ rawShapeNodes = cmds.ls(hist, type="blendShape")
+ rawPoseNodes = cmds.ls(hist, type="blendPose")
# Find any simplex ops connected to the history
# that have the given name
- ops = []
- for sn in rawShapeNodes:
- op = cmds.listConnections(
- "{0}.{1}".format(sn, "message"),
- source=False,
- destination=True,
- type="simplex_maya",
+ ops: list[str] = []
+ rawOps: list[str] = []
+ for sn in rawShapeNodes + rawPoseNodes:
+ op = (
+ cmds.listConnections(
+ "{0}.{1}".format(sn, "message"),
+ source=False,
+ destination=True,
+ type="simplex_maya",
+ )
+ or []
)
- if not op:
- continue
- op = op[0]
+ rawOps.extend(op)
+ rawOps = list(set(rawOps))
+
+ for op in rawOps:
js = cmds.getAttr(op + ".definition") or ""
sn = json.loads(js).get("systemName")
if sn == self.name:
@@ -465,20 +446,41 @@ def loadNodes(self, simp, thing, create=True, pBar=None):
raise RuntimeError(
"Found too many Simplex systems with the same name on the same object"
)
+ return ops
- # Back-select the shape nodes connected to those ops
+ def _getShapeNodes(self, ops):
shapeNodes = []
for op in ops:
- sn = cmds.listConnections(
- "{0}.{1}".format(op, "shapeMsg"),
- source=True,
- destination=False,
- type="blendShape",
- )
+ try:
+ sn = cmds.listConnections(
+ "{0}.{1}".format(op, "shapeMsg"),
+ source=True,
+ destination=False,
+ type="blendShape",
+ )
+ except ValueError:
+ continue
if sn:
shapeNodes.append(sn[0])
+ return shapeNodes
+
+ def _getPoseNodes(self, ops):
+ poseNodes = []
+ for op in ops:
+ try:
+ sn = cmds.listConnections(
+ "{0}.{1}".format(op, "poseMsg"),
+ source=True,
+ destination=False,
+ type="blendPose",
+ )
+ except ValueError:
+ continue
+ if sn:
+ poseNodes.append(sn[0])
+ return poseNodes
- # Find the msg connected control object
+ def _getCtrlNodes(self, ops):
ctrlCnx = []
for op in ops:
ccnx = cmds.listConnections(
@@ -488,76 +490,109 @@ def loadNodes(self, simp, thing, create=True, pBar=None):
)
if ccnx:
ctrlCnx.append(ccnx[0])
+ return ctrlCnx
- if not shapeNodes:
- if not create:
- raise RuntimeError(
- "Blendshape operator not found with creation turned off"
- )
- # Unlock the normals on the rest head because blendshapes don't work with locked normals
- # and you can't really do this after the blendshape has been created
- intermediates = [
- shp
- for shp in cmds.listRelatives(self.mesh, shapes=True, path=True)
- if cmds.getAttr(shp + ".intermediateObject")
- ]
- meshToFreeze = self.mesh if not intermediates else intermediates[0]
- isIntermediate = cmds.getAttr(meshToFreeze + ".intermediateObject")
- cmds.polyNormalPerVertex(meshToFreeze, ufn=True)
- cmds.polySoftEdge(meshToFreeze, a=180, ch=1)
- cmds.setAttr(meshToFreeze + ".intermediateObject", 0)
- cmds.delete(meshToFreeze, constructionHistory=True)
- cmds.setAttr(meshToFreeze + ".intermediateObject", isIntermediate)
-
- self.shapeNode = cmds.blendShape(
- self.mesh, name="{0}_BS".format(self.name), frontOfChain=True
- )[0]
- else:
- self.shapeNode = shapeNodes[0]
+ def _createShapeNode(self, name, mesh) -> str:
+ intermediates = [
+ shp
+ for shp in cmds.listRelatives(mesh, shapes=True, path=True)
+ if cmds.getAttr(shp + ".intermediateObject")
+ ]
+ meshToFreeze = mesh if not intermediates else intermediates[0]
+ isIntermediate = cmds.getAttr(meshToFreeze + ".intermediateObject")
+
+ # Unlock the normals on the rest head because blendshapes don't work with locked normals
+ # and you can't really do this after the blendshape has been created
+ cmds.polyNormalPerVertex(meshToFreeze, unFreezeNormal=True)
+ cmds.polySoftEdge(meshToFreeze, angle=180, constructionHistory=True)
+ cmds.setAttr(meshToFreeze + ".intermediateObject", 0)
+ cmds.delete(meshToFreeze, constructionHistory=True)
+ cmds.setAttr(meshToFreeze + ".intermediateObject", isIntermediate)
+
+ name = "{}_BS".format(name)
+ return cmds.blendShape(mesh, name=name, frontOfChain=True)[0]
+
+ def _createPoseNode(self, name) -> str:
+ name = "{}_BP".format(name)
+ return cmds.createNode("blendPose", name=name)
+
+ def _createSimplexNode(self, name):
+ op = cmds.createNode("simplex_maya", name=name)
+ cmds.addAttr(op, longName="revision", attributeType="long")
+ cmds.addAttr(op, longName="shapeMsg", attributeType="message")
+ cmds.addAttr(op, longName="poseMsg", attributeType="message")
+ cmds.addAttr(op, longName="ctrlMsg", attributeType="message")
+ return op
+
+ def _createControlNode(self, name, op):
+ tfmAttrs = [".tx", ".ty", ".tz", ".rx", ".ry", ".rz", ".sx", ".sy", ".sz", ".v"]
+ ctrl = cmds.group(empty=True, name="{0}_CTRL".format(name))
+ for attr in tfmAttrs:
+ cmds.setAttr(ctrl + attr, keyable=False, channelBox=False)
+ cmds.addAttr(ctrl, longName="solver", attributeType="message")
+ cmds.connectAttr(
+ "{0}.{1}".format(ctrl, "solver"),
+ "{0}.{1}".format(op, "ctrlMsg"),
+ )
+ return ctrl
- # find/build the operator
- # GODDAMMIT, why does maya return None instead of an empty list?????
- if not ops:
- if not create:
- raise RuntimeError(
- "Simplex operator not found with creation turned off"
+ # System IO
+ @undoable
+ def loadNodes(self, simp, thing, create=True, pBar=None):
+ """Create a new system based on the simplex tree
+ Build any DCC objects that are missing if create=True
+ Raises a runtime error if missing objects are found and
+ create=False
+
+ Parameters
+ ----------
+ simp :
+
+ thing :
+
+ create :
+ (Default value = True)
+ pBar :
+ (Default value = None)
+
+ Returns
+ -------
+
+ """
+ self.name = simp.name
+ self.mesh = thing
+
+ ops = self._getOpNodes(thing)
+ sns = self._getShapeNodes(ops)
+ cc = self._getCtrlNodes(ops)
+
+ if not create and (not sns or not ops or not cc):
+ types = []
+ if not sns:
+ types.append("blendShape")
+ if not ops:
+ types.append("simplex_maya")
+ if not cc:
+ types.append("CTRL")
+
+ raise RuntimeError(
+ "Creation turned off and some objects are missing: {}".format(
+ ", ".join(types)
)
- self.op = cmds.createNode("simplex_maya", name=self.name)
- cmds.addAttr(self.op, longName="revision", attributeType="long")
- cmds.addAttr(self.op, longName="shapeMsg", attributeType="message")
- cmds.addAttr(self.op, longName="ctrlMsg", attributeType="message")
- cmds.connectAttr(
- "{0}.{1}".format(self.shapeNode, "message"),
- "{0}.{1}".format(self.op, "shapeMsg"),
)
- else:
- self.op = ops[0]
- # find/build the ctrl object
- if not ctrlCnx:
- if not create:
- raise RuntimeError("Control object not found with creation turned off")
- self.ctrl = cmds.group(empty=True, name="{0}_CTRL".format(self.name))
- for attr in [
- ".tx",
- ".ty",
- ".tz",
- ".rx",
- ".ry",
- ".rz",
- ".sx",
- ".sy",
- ".sz",
- ".v",
- ]:
- cmds.setAttr(self.ctrl + attr, keyable=False, channelBox=False)
- cmds.addAttr(self.ctrl, longName="solver", attributeType="message")
- cmds.connectAttr(
- "{0}.{1}".format(self.ctrl, "solver"),
- "{0}.{1}".format(self.op, "ctrlMsg"),
- )
- else:
- self.ctrl = ctrlCnx[0]
+ self.op = ops[0] if ops else self._createSimplexNode(self.name)
+ self.ctrl = cc[0] if cc else self._createControlNode(self.name, self.op)
+
+ self.shapeNode = sns[0] if sns else self._createShapeNode(self.name, self.mesh)
+ if not cmds.isConnected(f"{self.shapeNode}.message", f"{self.op}.shapeMsg"):
+ cmds.connectAttr(f"{self.shapeNode}.message", f"{self.op}.shapeMsg")
+
+ if self.hasPoseNode:
+ pns = self._getPoseNodes(ops)
+ self.poseNode = pns[0] if pns else self._createPoseNode(self.name)
+ if not cmds.isConnected(f"{self.poseNode}.message", f"{self.op}.poseMsg"):
+ cmds.connectAttr(f"{self.poseNode}.message", f"{self.op}.poseMsg")
def getShapeThing(self, shapeName):
"""
@@ -593,9 +628,16 @@ def getSliderThing(self, sliderName):
return None
return things[0]
- @staticmethod
+ @classmethod
+ def buildDummyMesh(cls, name: str):
+ importHeadShape = cmds.createNode("mesh", name=name + "Shape")
+ badPar = cmds.listRelatives(importHeadShape, parent=True)[0]
+ importHead = cmds.rename(badPar, name)
+ return importHead, importHeadShape
+
+ @classmethod
@undoable
- def buildRestAbc(abcMesh, name):
+ def buildRestAbc(cls, abcMesh, name):
"""
Parameters
@@ -621,22 +663,28 @@ def buildRestAbc(abcMesh, name):
cmds.setAttr(abcNode + ".speed", 24) # Is this needed anymore?
cmds.setAttr(abcNode + ".time", 0)
- importHead = cmds.polySphere(
- name="{0}_SIMPLEX".format(name), constructionHistory=False
- )[0]
- importHeadShape = [i for i in cmds.listRelatives(importHead, shapes=True)][0]
+ importHead, importHeadShape = cls.buildDummyMesh("{0}_SIMPLEX".format(name))
+
+ importHead = "{0}_SIMPLEX".format(name)
+ importHeadShape = cmds.createNode("mesh", name=importHead + "Shape")
+ badPar = cmds.listRelatives(importHeadShape, parent=True)[0]
+ importHead = cmds.rename(badPar, importHead)
cmds.connectAttr(abcNode + ".outPolyMesh[0]", importHeadShape + ".inMesh")
cmds.polyEvaluate(importHead, vertex=True) # Force a refresh
cmds.disconnectAttr(abcNode + ".outPolyMesh[0]", importHeadShape + ".inMesh")
- cmds.sets(importHead, e=True, forceElement="initialShadingGroup")
+ cmds.sets(importHead, edit=True, forceElement="initialShadingGroup")
cmds.delete(abcNode)
return importHead
- @staticmethod
- def vertCount(mesh):
+ @classmethod
+ def vertCount(cls, mesh):
return cmds.polyEvaluate(mesh, vertex=True)
+ @undoable
+ def loadAbcPoses(self, abcMesh, js, pBar=None):
+ pass
+
@undoable
def loadAbc(self, abcMesh, js, pBar=None):
"""
@@ -679,7 +727,7 @@ def loadAbc(self, abcMesh, js, pBar=None):
}
fps = cmds.currentUnit(time=True, query=True)
- if isinstance(fps, six.string_types):
+ if isinstance(fps, str):
if fps.endswith("fps"):
fps = fps[:-3]
if fps in timeUnits:
@@ -694,8 +742,7 @@ def loadAbc(self, abcMesh, js, pBar=None):
if js["encodingVersion"] > 1:
shapes = [i["name"] for i in shapes]
- importHead = cmds.polySphere(name="importHead", constructionHistory=False)[0]
- importHeadShape = [i for i in cmds.listRelatives(importHead, shapes=True)][0]
+ importHead, importHeadShape = self.buildDummyMesh("importHead")
cmds.connectAttr(abcNode + ".outPolyMesh[0]", importHeadShape + ".inMesh")
cmds.polyEvaluate(importHead, vertex=True) # Force a refresh
@@ -705,7 +752,7 @@ def loadAbc(self, abcMesh, js, pBar=None):
cmds.delete(importRest, constructionHistory=True)
self._removeExtraShapeNodes(importRest)
- importBS = cmds.blendShape(importRest, importHead)[0]
+ importBS: str = cmds.blendShape(importRest, importHead)[0]
cmds.blendShape(importBS, edit=True, weight=[(0, 1.0)])
# Maybe get shapeNode from self.mesh??
importOrig = [
@@ -761,10 +808,10 @@ def getAllShapeVertices(self, shapes, pBar=None):
thing = om.MDagPath()
sl.getDagPath(0, thing)
meshFn = om.MFnMesh(thing)
- ptCount = meshFn.numVertices()
+ _ptCount = meshFn.numVertices()
with disconnected(self.shapeNode) as cnx:
shapeCnx = cnx[self.shapeNode]
- for v in six.itervalues(shapeCnx):
+ for v in shapeCnx.values():
cmds.setAttr(v, 0.0)
if pBar is not None:
@@ -782,12 +829,8 @@ def getAllShapeVertices(self, shapes, pBar=None):
cmds.setAttr(shape.thing, 1.0)
- if np is not None:
- rawPts = meshFn.getRawPoints()
- cta = (c_float * ptCount * 3).from_address(int(rawPts))
- out = np.ctypeslib.as_array(cta)
- out = np.copy(out)
- out = out.reshape((-1, 3))
+ if np is not None and mayaToNumpy is not None:
+ out = mayaToNumpy(meshFn.getRawPoints())
else:
flatverts = cmds.xform(
"{0}.vtx[*]".format(self.mesh),
@@ -814,7 +857,7 @@ def getShapeVertices(self, shape):
"""
with disconnected(self.shapeNode) as cnx:
shapeCnx = cnx[self.shapeNode]
- for v in six.itervalues(shapeCnx):
+ for v in shapeCnx.values():
cmds.setAttr(v, 0.0)
cmds.setAttr(shape.thing, 1.0)
if np is None:
@@ -864,8 +907,8 @@ def pushShapeVertices(self, shape):
# Push the vertices for a specific shape back to the DCC
pass
- @staticmethod
- def getMeshTopology(mesh, uvName=None):
+ @classmethod
+ def getMeshTopology(cls, mesh, uvName=None):
"""Get the topology of a mesh
Parameters
@@ -959,41 +1002,14 @@ def getNumpyShape(cls, mesh, world=False):
The point positions of the mesh
"""
- # This method uses some neat tricks to get data out of an MPointArray
- # Basically, you get an MScriptUtil pointer to the MPointArray data
- # Then you get a ctypes pointer to the MScriptUtil pointer
- # Then you get a numpy pointer to the ctypes pointer
- # Because these are all pointers, you're looking at the exact same
- # data in memory ... So just copy the data out to a numpy array
- # See: https://gist.github.com/tbttfox/9ca775bf629c7a1285c27c8d9d961bca
-
- # Get the MPointArray of the mesh
+ if np is None or mayaToNumpy is None:
+ raise RuntimeError("Can't do numpy stuff if its not importable")
vts = cls._getMeshVertices(mesh, world=world)
+ ret = mayaToNumpy(vts)
+ return ret[..., :3].copy()
- # Get the double4 pointer from the mpointarray
- count = vts.length()
- cc = count * 4
- util = om.MScriptUtil()
- util.createFromList([0.0] * cc, cc)
- ptr = util.asDouble4Ptr()
- vts.get(ptr)
-
- # Build a ctypes double4 pointer
- cdata = (c_double * 4) * count
-
- # int(ptr) gives the memory address
- cta = cdata.from_address(int(ptr))
-
- # This makes numpy look at the same memory as the ctypes array
- # so we can both read from and write to that data through numpy
- npArray = np.ctypeslib.as_array(cta)
-
- # Copy the data out of the numpy array, which is looking
- # into the cdata array, which is looking into the ptr array
- return npArray[..., :3].copy()
-
- @staticmethod
- def _getMeshVertices(mesh, world=False):
+ @classmethod
+ def _getMeshVertices(cls, mesh, world=False):
""" """
# Get the MDagPath from the name of the mesh
sl = om.MSelectionList()
@@ -1012,7 +1028,7 @@ def _getMeshVertices(mesh, world=False):
@classmethod
def _exportAbcVertices(cls, mesh, world=False):
""" """
- if np is None or imathnumpy is None:
+ if np is None or numpyToImath is None:
vts = cls._getMeshVertices(mesh, world=world)
vertices = V3fArray(vts.length())
for i in range(vts.length()):
@@ -1022,8 +1038,8 @@ def _exportAbcVertices(cls, mesh, world=False):
vertices = mkSampleVertexPoints(vts)
return vertices
- @staticmethod
- def getAbcFaces(mesh):
+ @classmethod
+ def getAbcFaces(cls, mesh):
""" """
# Get the MDagPath from the name of the mesh
sl = om.MSelectionList()
@@ -1119,7 +1135,7 @@ def exportAbc(
with disconnected(self.shapeNode) as cnx:
shapeCnx = cnx[self.shapeNode]
- for v in six.itervalues(shapeCnx):
+ for v in shapeCnx.values():
cmds.setAttr(v, 0.0)
for i, shape in enumerate(shapes):
if pBar is not None:
@@ -1165,7 +1181,7 @@ def exportOtherAbc(self, dccMesh, abcMesh, js, world=False, pBar=None):
with disconnected(self.op) as allSliderCnx:
sliderCnx = allSliderCnx[self.op]
# zero all slider vals on the op to get the rest shape
- for a in six.itervalues(sliderCnx):
+ for a in sliderCnx.values():
cmds.setAttr(a, 0.0)
restVerts = self.getNumpyShape(dccMesh, world=world)
@@ -1237,6 +1253,17 @@ def exportOtherAbc(self, dccMesh, abcMesh, js, world=False, pBar=None):
abcSample = OPolyMeshSchemaSample(shpVerts, faces, counts)
schema.set(abcSample)
+ def deleteObj(self, thing):
+ """Delete the given object"""
+ cmds.delete(thing)
+
+ def exportMesh(self, mesh, path):
+ """Export a mesh to the given path"""
+ faces, counts, uvs = self.getAbcFaces(mesh)
+ shape = self.getNumpyShape(mesh)
+ name = mesh.split('|')[-1]
+ buildAbc(path, shape, faces, counts, uvs, name=name)
+
# Revision tracking
def getRevision(self):
""" """
@@ -1431,7 +1458,7 @@ def extractWithDeltaShape(self, shape, live=True, offset=10.0):
"""
with disconnected(self.shapeNode) as cnx:
shapeCnx = cnx[self.shapeNode]
- for v in six.itervalues(shapeCnx):
+ for v in shapeCnx.values():
cmds.setAttr(v, 0.0)
# store the delta shape
@@ -1460,11 +1487,11 @@ def extractWithDeltaShape(self, shape, live=True, offset=10.0):
cmds.setAttr("{0}.{1}".format(bs, extracted), 1.0)
# Cleanup
- nodeDict = dict(Delta=delta, Init=init)
+ nodeDict = {"Delta": delta, "Init": init}
repDict = self._reparentDeltaShapes(extracted, nodeDict, bs)
# Shift the extracted shape to the side
- cmds.xform(extracted, relative=True, translation=[offset, 0, 0])
+ cmds.xform(extracted, relative=True, translation=(offset, 0, 0))
if live:
self.connectShape(shape, extracted, live, delete=False)
@@ -1529,7 +1556,7 @@ def extractWithDeltaConnection(self, shape, delta, value, live=True, offset=10.0
cmds.aliasAttr(delta, "{0}.{1}".format(bs, deltaPar))
# Cleanup
- nodeDict = dict(Init=init)
+ nodeDict = {"Init": init}
self._reparentDeltaShapes(extracted, nodeDict, bs)
# Remove the tweak node, otherwise editing the input progressives
@@ -1542,10 +1569,8 @@ def extractWithDeltaConnection(self, shape, delta, value, live=True, offset=10.0
cmds.delete(tweak)
# Shift the extracted shape to the side
- cmds.xform(extracted, relative=True, translation=[offset, 0, 0])
-
- if live:
- self.connectShape(shape, extracted, live, delete=False)
+ cmds.xform(extracted, relative=True, translation=(offset, 0, 0))
+ self.connectShape(shape, extracted, live, delete=False)
return extracted
@@ -1577,8 +1602,7 @@ def extractShape(self, shape, live=True, offset=10.0):
)[0]
# Shift the extracted shape to the side
- cmds.xform(extracted, relative=True, translation=[offset, 0, 0])
-
+ cmds.xform(extracted, relative=True, translation=(offset, 0, 0))
if live:
self.connectShape(shape, extracted, live, delete=False)
return extracted
@@ -1612,8 +1636,13 @@ def connectShape(self, shape, mesh=None, live=False, delete=False):
attrName = cmds.attributeName(shape.thing, long=True)
mesh = "{0}_Extract".format(attrName)
- if not cmds.objExists(mesh):
+ chk = cmds.ls(mesh)
+ if not chk:
return
+ if len(chk) > 1:
+ msg = "Multiple objects with the same name found in file:\n"
+ msg += '\n'.join(chk)
+ raise ValueError(msg)
index = self.getShapeIndex(shape)
tgn = "{0}.inputTarget[0].inputTargetGroup[{1}]".format(self.shapeNode, index)
@@ -1629,6 +1658,7 @@ def connectShape(self, shape, mesh=None, live=False, delete=False):
if not live:
cmds.disconnectAttr(outAttr, inAttr)
+
if delete:
cmds.delete(mesh)
@@ -1835,7 +1865,7 @@ def getFalloffThing(self, falloff):
-------
"""
- shape = [i for i in cmds.listRelatives(self.mesh, shapes=True)][0]
+ shape = cmds.listRelatives(self.mesh, shapes=True)[0]
return shape + "." + falloff.name
# Sliders
@@ -2093,18 +2123,31 @@ def _clearShapes(self, item, doOrig=False):
aname = cmds.ls(item, long=1)[0]
shapes = cmds.ls(cmds.listRelatives(item, shapes=1), long=1)
baseName = aname.split("|")[-1]
+ baseName, digits = split_trailing_digits(baseName)
- primary = "{0}|{1}Shape".format(aname, baseName)
- orig = "{0}|{1}ShapeOrig".format(aname, baseName)
+ primary = "{0}|{1}Shape{2}".format(aname, baseName, digits)
+ origs = []
+ others = []
for shape in shapes:
+ base, digits = split_trailing_digits(shape)
+ if base.endswith('Orig'):
+ origs.append(shape)
+ else:
+ others.append(shape)
+
+ for shape in others:
if shape == primary:
continue
- elif shape == orig:
- if doOrig:
- cmds.delete(shape)
- else:
- cmds.delete(shape)
+ cmds.delete(shape)
+
+ if doOrig:
+ cmds.delete(origs)
+ else:
+ # Don't delete the first orig
+ if len(origs) > 1:
+ origs = sorted(origs)
+ cmds.delete(origs[1:])
@undoable
def forceRebuildSliderConnections(self):
@@ -2160,7 +2203,7 @@ def _reparentDeltaShapes(self, par, nodeDict, bsNode, toDelete=None):
shapeDict = {}
origDict = {}
- for name, node in six.iteritems(nodeDict):
+ for name, node in nodeDict.items():
shape = cmds.listRelatives(node, noIntermediate=1, shapes=1)[0]
shape = cmds.ls(shape, absoluteName=1)[0]
if shape:
@@ -2236,7 +2279,7 @@ def _createTravDelta(self, trav, target, tVal, doReparent=True):
sliderCnx = cnx[self.op]
# zero all slider vals on the op
- for a in six.itervalues(sliderCnx):
+ for a in sliderCnx.values():
cmds.setAttr(a, 0.0)
with disconnected(floatShapes + tShapes):
@@ -2249,7 +2292,7 @@ def _createTravDelta(self, trav, target, tVal, doReparent=True):
for pair in trav.endPoint.pairs:
sliDict[pair.slider].append(pair.value)
- for slider, (start, end) in six.iteritems(sliDict):
+ for slider, (start, end) in sliDict.items():
vv = start + tVal * (end - start)
cmds.setAttr(sliderCnx[slider.thing], vv)
@@ -2273,7 +2316,7 @@ def _createTravDelta(self, trav, target, tVal, doReparent=True):
# Cleanup
if doReparent:
- nodeDict = dict(Delta=deltaObj)
+ nodeDict = {"Delta": deltaObj}
repDict = self._reparentDeltaShapes(target, nodeDict, bs, [rest, base])
return repDict["Delta"]
return deltaObj
@@ -2311,18 +2354,17 @@ def extractTraversalShape(self, trav, shape, live=True, offset=10.0):
with disconnected(self.op) as cnx:
sliderCnx = cnx[self.op]
# zero all slider vals on the op
- for a in six.itervalues(sliderCnx):
+ for a in sliderCnx.values():
cmds.setAttr(a, 0.0)
with disconnected(floatShapes): # tShapes
-
sliDict = {}
for pair in trav.startPoint.pairs:
sliDict[pair.slider] = [pair.value]
for pair in trav.endPoint.pairs:
sliDict[pair.slider].append(pair.value)
- for slider, (start, end) in six.iteritems(sliDict):
+ for slider, (start, end) in sliDict.items():
vv = start + val * (end - start)
cmds.setAttr(sliderCnx[slider.thing], vv)
@@ -2331,8 +2373,9 @@ def extractTraversalShape(self, trav, shape, live=True, offset=10.0):
)
extracted = extracted[0]
self._clearShapes(extracted)
- cmds.xform(extracted, relative=True, translation=[offset, 0, 0])
- self.connectTraversalShape(trav, shape, extracted, live=live, delete=False)
+ cmds.xform(extracted, relative=True, translation=(offset, 0, 0))
+ if live:
+ self.connectTraversalShape(trav, shape, extracted, live=live, delete=False)
cmds.select(extracted)
return extracted
@@ -2360,10 +2403,22 @@ def connectTraversalShape(self, trav, shape, mesh=None, live=True, delete=False)
if mesh is None:
attrName = cmds.attributeName(shape.thing, long=True)
mesh = "{0}_Extract".format(attrName)
+
+ chk = cmds.ls(mesh)
+ if not chk:
+ return
+ if len(chk) > 1:
+ msg = "Multiple objects with the same name found in file:\n"
+ msg += '\n'.join(chk)
+ raise ValueError(msg)
+
shapeIdx = trav.prog.getShapeIndex(shape)
tVal = trav.prog.pairs[shapeIdx].value
delta = self._createTravDelta(trav, mesh, tVal)
- self.connectShape(shape, delta, live, delete)
+
+ if live:
+ self.connectShape(shape, delta, live, delete)
+
if delete:
cmds.delete(mesh)
@@ -2406,7 +2461,7 @@ def _createComboDelta(self, combo, target, tVal, doReparent=True):
sliderCnx = cnx[self.op]
# zero all slider vals on the op
- for a in six.itervalues(sliderCnx):
+ for a in sliderCnx.values():
cmds.setAttr(a, 0.0)
# pull out the rest shape
@@ -2438,7 +2493,7 @@ def _createComboDelta(self, combo, target, tVal, doReparent=True):
# Cleanup
if doReparent:
- nodeDict = dict(Delta=deltaObj)
+ nodeDict = {"Delta": deltaObj}
repDict = self._reparentDeltaShapes(target, nodeDict, bs, [rest, base])
return repDict["Delta"]
return deltaObj
@@ -2471,7 +2526,7 @@ def extractComboShape(self, combo, shape, live=True, offset=10.0):
with disconnected(self.op) as cnx:
sliderCnx = cnx[self.op]
# zero all slider vals on the op
- for a in six.itervalues(sliderCnx):
+ for a in sliderCnx.values():
cmds.setAttr(a, 0.0)
with disconnected(floatShapes):
@@ -2482,9 +2537,13 @@ def extractComboShape(self, combo, shape, live=True, offset=10.0):
extracted = cmds.duplicate(
self.mesh, name="{0}_Extract".format(shape.name)
)[0]
+
self._clearShapes(extracted)
- cmds.xform(extracted, relative=True, translation=[offset, 0, 0])
- self.connectComboShape(combo, shape, extracted, live=live, delete=False)
+ cmds.xform(extracted, relative=True, translation=(offset, 0, 0))
+
+ if live:
+ self.connectComboShape(combo, shape, extracted, live=live, delete=False)
+
cmds.select(extracted)
return extracted
@@ -2512,15 +2571,28 @@ def connectComboShape(self, combo, shape, mesh=None, live=True, delete=False):
if mesh is None:
attrName = cmds.attributeName(shape.thing, long=True)
mesh = "{0}_Extract".format(attrName)
- shapeIdx = combo.prog.getShapeIndex(shape)
- tVal = combo.prog.pairs[shapeIdx].value
+
+ chk = cmds.ls(mesh)
+ if not chk:
+ return
+ if len(chk) > 1:
+ msg = "Multiple objects with the same name found in file:\n"
+ msg += '\n'.join(chk)
+ raise ValueError(msg)
+ mesh = chk[0]
+
+ progShapeIdx = combo.prog.getShapeIndex(shape)
+ tVal = combo.prog.pairs[progShapeIdx].value
+
delta = self._createComboDelta(combo, mesh, tVal)
- self.connectShape(shape, delta, live, delete)
+ if live:
+ self.connectShape(shape, delta, live, delete)
+
if delete:
cmds.delete(mesh)
- @staticmethod
- def setDisabled(op):
+ @classmethod
+ def setDisabled(cls, op):
"""
Parameters
@@ -2542,8 +2614,8 @@ def setDisabled(op):
helpers.append((prop, val))
return helpers
- @staticmethod
- def reEnable(helpers):
+ @classmethod
+ def reEnable(cls, helpers):
"""
Parameters
@@ -2576,13 +2648,13 @@ def renameCombo(self, combo, name):
pass
# Data Access
- @staticmethod
- def getSimplexOperators():
+ @classmethod
+ def getSimplexOperators(cls):
""" """
return cmds.ls(type="simplex_maya")
- @staticmethod
- def getSimplexOperatorsByName(name):
+ @classmethod
+ def getSimplexOperatorsByName(cls, name):
"""
Parameters
@@ -2598,8 +2670,8 @@ def getSimplexOperatorsByName(name):
"""
return cmds.ls(name, type="simplex_maya")
- @staticmethod
- def getSimplexOperatorsOnObject(thing):
+ @classmethod
+ def getSimplexOperatorsOnObject(cls, thing):
"""
Parameters
@@ -2629,8 +2701,8 @@ def getSimplexOperatorsOnObject(thing):
out.append(op)
return out
- @staticmethod
- def getSimplexString(op):
+ @classmethod
+ def getSimplexString(cls, op):
"""
Parameters
@@ -2646,8 +2718,8 @@ def getSimplexString(op):
"""
return cmds.getAttr(op + ".definition")
- @staticmethod
- def getSimplexStringOnThing(thing, systemName):
+ @classmethod
+ def getSimplexStringOnThing(cls, thing, systemName):
"""
Parameters
@@ -2671,8 +2743,8 @@ def getSimplexStringOnThing(thing, systemName):
return js
return None
- @staticmethod
- def setSimplexString(op, val):
+ @classmethod
+ def setSimplexString(cls, op, val):
"""
Parameters
@@ -2690,8 +2762,8 @@ def setSimplexString(op, val):
"""
return cmds.setAttr(op + ".definition", val, type="string")
- @staticmethod
- def selectObject(thing):
+ @classmethod
+ def selectObject(cls, thing):
"""Select an object in the DCC
Parameters
@@ -2710,8 +2782,8 @@ def selectCtrl(self):
if self.ctrl:
self.selectObject(self.ctrl)
- @staticmethod
- def getObjectByName(name):
+ @classmethod
+ def getObjectByName(cls, name):
"""
Parameters
@@ -2728,10 +2800,12 @@ def getObjectByName(name):
objs = cmds.ls(name)
if not objs:
return None
+ if len(objs) > 1:
+ raise ValueError("Multiple objects with the same name found")
return objs[0]
- @staticmethod
- def getObjectName(thing):
+ @classmethod
+ def getObjectName(cls, thing):
"""
Parameters
@@ -2747,13 +2821,13 @@ def getObjectName(thing):
"""
return thing
- @staticmethod
- def staticUndoOpen():
+ @classmethod
+ def staticUndoOpen(cls):
""" """
cmds.undoInfo(chunkName="SimplexOperation", openChunk=True)
- @staticmethod
- def staticUndoClose():
+ @classmethod
+ def staticUndoClose(cls):
""" """
cmds.undoInfo(closeChunk=True)
@@ -2859,8 +2933,8 @@ def loadPersistentSlider(cls, thing):
"""
return cls.getObjectByName(thing)
- @staticmethod
- def getSelectedObjects():
+ @classmethod
+ def getSelectedObjects(cls):
""" """
# For maya, only return transform nodes
return cmds.ls(sl=True, transforms=True)
@@ -2886,8 +2960,8 @@ def importObj(self, path):
imp = new.pop()
return imp
- @staticmethod
- def _getDeformerChain(chkObj):
+ @classmethod
+ def _getDeformerChain(cls, chkObj):
# Get a deformer chain
memo = []
while chkObj and chkObj not in memo:
@@ -2929,7 +3003,7 @@ def primeShapes(self, combo):
with disconnected(self.shapeNode) as cnx:
shapeCnx = cnx[self.shapeNode]
- for v in six.itervalues(shapeCnx):
+ for v in shapeCnx.values():
cmds.setAttr(v, 0.0)
for shape in upstreams:
@@ -3168,8 +3242,8 @@ def buildDeleterScriptJob():
)
-def simplexDelCB(node, dgMod, clientData):
- xNode, dName = clientData
+def simplexDelCB(_node, dgMod, clientData):
+ _xNode, dName = clientData
dNode = getMObject(dName)
if dNode and not dNode.isNull():
dgMod.deleteNode(dNode)
diff --git a/scripts/simplexui/interfaceModel.py b/src/python/simplexui/interfaceModel.py
similarity index 89%
rename from scripts/simplexui/interfaceModel.py
rename to src/python/simplexui/interfaceModel.py
index 526d5fbe..e89370e9 100644
--- a/scripts/simplexui/interfaceModel.py
+++ b/src/python/simplexui/interfaceModel.py
@@ -16,13 +16,9 @@
# along with Simplex. If not, see .
# pylint:disable=missing-docstring,unused-argument,no-self-use,too-many-return-statements
-from __future__ import absolute_import
-
import re
from contextlib import contextmanager
-from six.moves import range
-
from .items import (
Combo,
ComboPair,
@@ -168,7 +164,9 @@ def coerceIndexToRoots(indexes):
The coerced list
"""
indexes = [i for i in indexes if i.column() == 0]
- indexes = sorted(indexes, key=lambda x: x.model().itemFromIndex(x).classDepth, reverse=True)
+ indexes = sorted(
+ indexes, key=lambda x: x.model().itemFromIndex(x).classDepth, reverse=True
+ )
# Check each item to see if any of it's ancestors
# are in the selection list. If not, it's a root
roots = []
@@ -391,29 +389,33 @@ def data(self, index, role):
def flags(self, index):
if not index.isValid():
- return Qt.ItemIsEnabled
+ return Qt.ItemFlag.ItemIsEnabled
if index.column() == 0:
item = index.internalPointer()
if isinstance(item, (Slider, Combo, Traversal)):
return (
- Qt.ItemIsEnabled
- | Qt.ItemIsSelectable
- | Qt.ItemIsEditable
- | Qt.ItemIsUserCheckable
+ Qt.ItemFlag.ItemIsEnabled
+ | Qt.ItemFlag.ItemIsSelectable
+ | Qt.ItemFlag.ItemIsEditable
+ | Qt.ItemFlag.ItemIsUserCheckable
)
# TODO: make the SHAPES object under a combo or traversal not-editable
- return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
+ return (
+ Qt.ItemFlag.ItemIsEnabled
+ | Qt.ItemFlag.ItemIsSelectable
+ | Qt.ItemFlag.ItemIsEditable
+ )
- def setData(self, index, value, role=Qt.EditRole):
+ def setData(self, index, value, role=Qt.ItemDataRole.EditRole):
if not index.isValid():
return False
- if role == Qt.CheckStateRole:
+ if role == Qt.ItemDataRole.CheckStateRole:
item = index.internalPointer()
if index.column() == 0:
if isinstance(item, (Slider, Combo, Traversal)):
- item.enabled = value == Qt.Checked
+ item.enabled = value == Qt.CheckState.Checked
return True
- elif role == Qt.EditRole:
+ elif role == Qt.ItemDataRole.EditRole:
item = index.internalPointer()
if index.column() == 0:
if isinstance(item, (Group, Slider, Combo, Traversal, ProgPair)):
@@ -434,8 +436,8 @@ def setData(self, index, value, role=Qt.EditRole):
return False
def headerData(self, section, orientation, role):
- if orientation == Qt.Horizontal:
- if role == Qt.DisplayRole:
+ if orientation == Qt.Orientation.Horizontal:
+ if role == Qt.ItemDataRole.DisplayRole:
sects = ("Items", "Slide", "Value")
return sects[section]
return None
@@ -473,17 +475,17 @@ def getItemData(self, item, column, role):
if item is None:
return None
- if role in (Qt.DisplayRole, Qt.EditRole):
+ if role in (Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole):
return item.treeData(column)
- elif role == Qt.CheckStateRole:
+ elif role == Qt.ItemDataRole.CheckStateRole:
chk = None
if column == 0:
chk = item.treeChecked()
if chk is not None:
- chk = Qt.Checked if chk else Qt.Unchecked
+ chk = Qt.CheckState.Checked if chk else Qt.CheckState.Unchecked
return chk
- elif role == Qt.DecorationRole:
+ elif role == Qt.ItemDataRole.DecorationRole:
if column == 0:
return item.icon()
return None
@@ -766,7 +768,8 @@ def getItemRow(self, item):
def getItemAppendRow(self, item):
return len(self.simplex.sliderGroups) + 1
- def index(self, row, column=0, parIndex=QModelIndex()):
+ def index(self, row, column=0, parIndex=None):
+ parIndex = QModelIndex() if parIndex is None else parIndex
if row <= 0:
return self.createIndex(row, column, None)
try:
@@ -788,12 +791,12 @@ def data(self, index, role):
if not index.isValid():
return None
group = index.internalPointer()
- if group and role in (Qt.DisplayRole, Qt.EditRole):
+ if group and role in (Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole):
return group.name
return None
def flags(self, index):
- return Qt.ItemIsEnabled | Qt.ItemIsSelectable
+ return Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable
def itemFromIndex(self, index):
return index.internalPointer()
@@ -829,9 +832,9 @@ def buildLine(self):
partials = []
for fo in self.simplex.falloffs:
cs = self._getCheckState(fo)
- if cs == Qt.Checked:
+ if cs == Qt.CheckState.Checked:
fulls.append(fo.name)
- elif cs == Qt.PartiallyChecked:
+ elif cs == Qt.CheckState.PartiallyChecked:
partials.append(fo.name)
if partials:
title = "{0} <<{1}>>".format(",".join(fulls), ",".join(partials))
@@ -854,7 +857,8 @@ def getItemAppendRow(self, item):
except AttributeError:
return 0
- def index(self, row, column=0, parIndex=QModelIndex()):
+ def index(self, row, column=0, parIndex=None):
+ parIndex = QModelIndex() if parIndex is None else parIndex
if row <= 0:
return self.createIndex(row, column, None)
try:
@@ -880,10 +884,10 @@ def columnCount(self, parent):
def _getCheckState(self, fo):
sli = self._checks.get(fo, [])
if len(sli) == len(self.sliders):
- return Qt.Checked
+ return Qt.CheckState.Checked
elif len(sli) == 0:
- return Qt.Unchecked
- return Qt.PartiallyChecked
+ return Qt.CheckState.Unchecked
+ return Qt.CheckState.PartiallyChecked
def data(self, index, role):
if not index.isValid():
@@ -892,23 +896,23 @@ def data(self, index, role):
if not falloff:
return None
- if role in (Qt.DisplayRole, Qt.EditRole):
+ if role in (Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole):
return falloff.name
- elif role == Qt.CheckStateRole:
+ elif role == Qt.ItemDataRole.CheckStateRole:
return self._getCheckState(falloff)
return None
def setData(self, index, value, role):
- if role == Qt.CheckStateRole:
+ if role == Qt.ItemDataRole.CheckStateRole:
fo = index.internalPointer()
if not fo:
return
- if value == Qt.Checked:
+ if value == Qt.CheckState.Checked:
for s in self.sliders:
if fo not in s.prog.falloffs:
s.prog.addFalloff(fo)
self._checks.setdefault(fo, []).append(s)
- elif value == Qt.Unchecked:
+ elif value == Qt.CheckState.Unchecked:
for s in self.sliders:
if fo in s.prog.falloffs:
s.prog.removeFalloff(fo)
@@ -920,7 +924,11 @@ def setData(self, index, value, role):
return False
def flags(self, index):
- return Qt.ItemIsEnabled | Qt.ItemIsEditable | Qt.ItemIsUserCheckable
+ return (
+ Qt.ItemFlag.ItemIsEnabled
+ | Qt.ItemFlag.ItemIsEditable
+ | Qt.ItemFlag.ItemIsUserCheckable
+ )
def itemFromIndex(self, index):
return index.internalPointer()
@@ -941,7 +949,8 @@ def getItemAppendRow(self, item):
except AttributeError:
return 0
- def index(self, row, column=0, parIndex=QModelIndex()):
+ def index(self, row, column=0, parIndex=None):
+ parIndex = QModelIndex() if parIndex is None else parIndex
if row < 0:
return QModelIndex()
try:
@@ -971,7 +980,7 @@ def data(self, index, role):
if not falloff:
return None
- if role in (Qt.DisplayRole, Qt.EditRole):
+ if role in (Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole):
if index.column() == 0:
return falloff.name
elif index.column() == 1:
@@ -1000,7 +1009,7 @@ def setData(self, index, value, role):
falloff = index.internalPointer()
if not falloff:
return False
- if role == Qt.EditRole:
+ if role == Qt.ItemDataRole.EditRole:
if index.column() == 0:
falloff.name = value
elif index.column() == 1:
@@ -1025,7 +1034,11 @@ def setData(self, index, value, role):
return False
def flags(self, index):
- return Qt.ItemIsEnabled | Qt.ItemIsEditable | Qt.ItemIsSelectable
+ return (
+ Qt.ItemFlag.ItemIsEnabled
+ | Qt.ItemFlag.ItemIsEditable
+ | Qt.ItemFlag.ItemIsSelectable
+ )
def itemFromIndex(self, index):
return index.internalPointer()
diff --git a/scripts/simplexui/interfaceModelTrees.py b/src/python/simplexui/interfaceModelTrees.py
similarity index 93%
rename from scripts/simplexui/interfaceModelTrees.py
rename to src/python/simplexui/interfaceModelTrees.py
index 45093226..f11d7439 100644
--- a/scripts/simplexui/interfaceModelTrees.py
+++ b/src/python/simplexui/interfaceModelTrees.py
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from six.moves import range
-
from .dragFilter import DragFilter
from .items import Group
from Qt.QtCore import QItemSelection, QItemSelectionModel, QModelIndex, QRegExp, Qt
@@ -44,8 +42,8 @@ class SimplexTree(QTreeView):
def __init__(self, parent):
super(SimplexTree, self).__init__(parent)
- self.expandModifier = Qt.ControlModifier
- self.depthModifier = Qt.ShiftModifier
+ self.expandModifier = Qt.KeyboardModifier.ControlModifier
+ self.depthModifier = Qt.KeyboardModifier.ShiftModifier
self._menu = None
self._plugins = []
@@ -81,7 +79,9 @@ def unifySelection(self):
And it will clear the selection on this tree if no modifiers are being held
"""
mods = QApplication.keyboardModifiers()
- if not mods & (Qt.ControlModifier | Qt.ShiftModifier):
+ if not mods & (
+ Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.ShiftModifier
+ ):
selModel = self.selectionModel()
selModel.blockSignals(True)
try:
@@ -311,7 +311,7 @@ def dragTick(self, ticks, mul):
# Menus and Actions
def connectMenus(self):
"""Setup the QT signal/slot connections to the context menus"""
- self.setContextMenuPolicy(Qt.CustomContextMenu)
+ self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.customContextMenuRequested.connect(self.openMenu)
def openMenu(self, pos):
@@ -408,7 +408,9 @@ def setItemSelection(self, items):
toSel = QItemSelection()
for idx in idxs:
- toSel.merge(QItemSelection(idx, idx), QItemSelectionModel.Select)
+ toSel.merge(
+ QItemSelection(idx, idx), QItemSelectionModel.SelectionFlag.Select
+ )
for idx in idxs:
par = idx.parent()
@@ -416,7 +418,7 @@ def setItemSelection(self, items):
self.scrollToIndex(par)
selModel = self.selectionModel()
- selModel.select(toSel, QItemSelectionModel.ClearAndSelect)
+ selModel.select(toSel, QItemSelectionModel.SelectionFlag.ClearAndSelect)
# Currently, there's no difference between these
diff --git a/scripts/simplexui/items/__init__.py b/src/python/simplexui/items/__init__.py
similarity index 82%
rename from scripts/simplexui/items/__init__.py
rename to src/python/simplexui/items/__init__.py
index 35f87532..0284cd84 100644
--- a/scripts/simplexui/items/__init__.py
+++ b/src/python/simplexui/items/__init__.py
@@ -24,3 +24,19 @@
from .slider import Slider
from .stack import Stack, stackable
from .traversal import Traversal, TravPair
+
+__all__ = [
+ "Combo",
+ "ComboPair",
+ "Falloff",
+ "Group",
+ "ProgPair",
+ "Progression",
+ "Shape",
+ "Simplex",
+ "Slider",
+ "Stack",
+ "stackable",
+ "Traversal",
+ "TravPair",
+]
diff --git a/scripts/simplexui/items/accessor.py b/src/python/simplexui/items/accessor.py
similarity index 94%
rename from scripts/simplexui/items/accessor.py
rename to src/python/simplexui/items/accessor.py
index 7f53733f..c9c0d1fb 100644
--- a/scripts/simplexui/items/accessor.py
+++ b/src/python/simplexui/items/accessor.py
@@ -15,12 +15,8 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import absolute_import
-
import copy
-import six
-
class SimplexAccessor(object):
"""The base object for all Simplex System object types
@@ -99,7 +95,7 @@ def __deepcopy__(self, memo):
cls = self.__class__
result = cls.__new__(cls)
memo[id(self)] = result
- for k, v in six.iteritems(self.__dict__):
+ for k, v in self.__dict__.items():
if k == "_thing" and self.DCC.program != "dummy":
# DO NOT make a copy of the DCC thing (unless its a dummy dcc)
# as it may or may not be a persistent object
diff --git a/scripts/simplexui/items/combo.py b/src/python/simplexui/items/combo.py
similarity index 95%
rename from scripts/simplexui/items/combo.py
rename to src/python/simplexui/items/combo.py
index 59b20807..4a040853 100644
--- a/scripts/simplexui/items/combo.py
+++ b/src/python/simplexui/items/combo.py
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from six.moves import zip
-
from ..interface import undoContext
# pylint:disable=missing-docstring,unused-argument,no-self-use
@@ -159,12 +157,12 @@ class Combo(SimplexAccessor):
)
_freezeIcon = None
- def __init__(
- self, name, simplex, pairs, prog, group, solveType, color=QColor(128, 128, 128)
- ):
+ def __init__(self, name, simplex, pairs, prog, group, solveType, color=None):
super(Combo, self).__init__(simplex)
+ color = QColor(128, 128, 128) if color is None else color
+
with self.stack.store(self):
- if group.groupType != type(self):
+ if group.groupType is not type(self):
raise ValueError("Cannot add this slider to a combo group")
self._name = name
self.pairs = pairs
@@ -179,7 +177,6 @@ def __init__(
mgrs = [model.insertItemManager(group) for model in self.models]
with nested(*mgrs):
-
self.group = group
for p in self.pairs:
p.combo = self
@@ -244,9 +241,9 @@ def comboAlreadyExists(cls, simplex, sliders, values):
The combo that exists with the given values, or None if none exist
"""
- checker = set([(s.name, v) for s, v in zip(sliders, values)])
+ checker = {(s.name, v) for s, v in zip(sliders, values)}
for combo in simplex.combos:
- tester = set([(p.slider.name, p.value) for p in combo.pairs])
+ tester = {(p.slider.name, p.value) for p in combo.pairs}
if checker == tester:
return combo
return None
diff --git a/scripts/simplexui/items/falloff.py b/src/python/simplexui/items/falloff.py
similarity index 96%
rename from scripts/simplexui/items/falloff.py
rename to src/python/simplexui/items/falloff.py
index 26f4ea59..f92a1d1a 100644
--- a/scripts/simplexui/items/falloff.py
+++ b/src/python/simplexui/items/falloff.py
@@ -16,8 +16,6 @@
# along with Simplex. If not, see .
# pylint:disable=missing-docstring,unused-argument,no-self-use
-from __future__ import absolute_import
-
import copy
import math
diff --git a/scripts/simplexui/items/group.py b/src/python/simplexui/items/group.py
similarity index 94%
rename from scripts/simplexui/items/group.py
rename to src/python/simplexui/items/group.py
index bcef2c23..f6ba556e 100644
--- a/scripts/simplexui/items/group.py
+++ b/src/python/simplexui/items/group.py
@@ -48,12 +48,14 @@ class Group(SimplexAccessor):
classDepth = 1
- def __init__(self, name, simplex, groupType, color=QColor(128, 128, 128)):
+ def __init__(self, name, simplex, groupType, color=None):
super(Group, self).__init__(simplex)
from .combo import Combo
from .slider import Slider
from .traversal import Traversal
+ color = QColor(128, 128, 128) if color is None else color
+
with self.stack.store(self):
self._name = name
self.items = []
@@ -284,7 +286,7 @@ def take(self, things):
if self.groupType is None:
self.groupType = type(things[0])
- if not all([isinstance(i, self.groupType) for i in things]):
+ if not all(isinstance(i, self.groupType) for i in things):
raise ValueError(
"All items in this group must be of type: {}".format(self.groupType)
)
diff --git a/scripts/simplexui/items/progression.py b/src/python/simplexui/items/progression.py
similarity index 96%
rename from scripts/simplexui/items/progression.py
rename to src/python/simplexui/items/progression.py
index a5967fa4..4b60f250 100644
--- a/scripts/simplexui/items/progression.py
+++ b/src/python/simplexui/items/progression.py
@@ -16,8 +16,6 @@
# along with Simplex. If not, see .
-from six.moves import range, zip
-
# pylint:disable=missing-docstring,unused-argument,no-self-use
from ..utils import getNextName, nested
from .accessor import SimplexAccessor
diff --git a/scripts/simplexui/items/shape.py b/src/python/simplexui/items/shape.py
similarity index 95%
rename from scripts/simplexui/items/shape.py
rename to src/python/simplexui/items/shape.py
index bc7e5afc..421f4473 100644
--- a/scripts/simplexui/items/shape.py
+++ b/src/python/simplexui/items/shape.py
@@ -16,8 +16,6 @@
# along with Simplex. If not, see .
-from six.moves import zip
-
from ..interface import DCC, undoContext
# pylint:disable=missing-docstring,unused-argument,no-self-use
@@ -54,8 +52,9 @@ class Shape(SimplexAccessor):
classDepth = 10
- def __init__(self, name, simplex, create=True, color=QColor(128, 128, 128)):
+ def __init__(self, name, simplex, create=True, color=None):
super(Shape, self).__init__(simplex)
+ color = QColor(128, 128, 128) if color is None else color
with self.stack.store(self):
self._thing = None
self._verts = None
diff --git a/scripts/simplexui/items/simplex.py b/src/python/simplexui/items/simplex.py
similarity index 95%
rename from scripts/simplexui/items/simplex.py
rename to src/python/simplexui/items/simplex.py
index 635f7700..0b3878db 100644
--- a/scripts/simplexui/items/simplex.py
+++ b/src/python/simplexui/items/simplex.py
@@ -16,15 +16,10 @@
# along with Simplex. If not, see .
# pylint:disable=missing-docstring,unused-argument,no-self-use
-from __future__ import absolute_import, print_function
-
import copy
import itertools
import json
-import six
-from six.moves import map, zip
-
try:
import numpy as np
except ImportError:
@@ -121,7 +116,7 @@ def __deepcopy__(self, memo):
cls = self.__class__
result = cls.__new__(cls)
memo[id(self)] = result
- for k, v in six.iteritems(self.__dict__):
+ for k, v in self.__dict__.items():
if k == "models":
# do not make a copy of the connected models
# a deepcopied simplex won't be connected to a UI
@@ -139,7 +134,7 @@ def __deepcopy__(self, memo):
# do not connect the deepcopied simplex to the DCC
# we will want to change it without affecting the current scene
# Requires the name be copied already
- setattr(result, "_name", copy.deepcopy(self._name, memo))
+ setattr(result, "_name", copy.deepcopy(self._name, memo)) # noqa: B010
if v.program == "dummy":
# If it's already a dummy, go ahead and just deepcopy
setattr(result, k, copy.deepcopy(v, memo))
@@ -526,6 +521,29 @@ def loadSmpxShapes(self, smpxPath, pBar=None):
finally:
del abcMesh, iarch
+ def loadSmpxPoses(self, smpxPath, pBar=None):
+ """Load the Poses from a .smpx file onto an already loaded system
+ This is the "Update the joints and skin" method
+
+ Parameters
+ ----------
+ smpxPath : str
+ The path to the .smpx file
+ pBar : QProgressDialog, optional
+ If provided, display progress in this dialog
+
+ Returns
+ -------
+
+ """
+ iarch, abcMesh, jsString = getSmpxArchiveData(smpxPath)
+ js = json.loads(jsString)
+
+ try:
+ self.DCC.loadAbcPoses(abcMesh, js, pBar=pBar)
+ finally:
+ del abcMesh, iarch
+
def loadSmpxFalloffs(self, abcPath, pBar=None):
"""Load the relevant data from a simplex alembic
@@ -632,9 +650,9 @@ def comboExists(self, sliders, values):
The Combo with those sliders and values, or None if none found
"""
- checkSet = set([(s.name, v) for s, v in zip(sliders, values)])
+ checkSet = {(s.name, v) for s, v in zip(sliders, values)}
for cmb in self.combos:
- cmbSet = set([(p.slider.name, p.value) for p in cmb.pairs])
+ cmbSet = {(p.slider.name, p.value) for p in cmb.pairs}
if checkSet == cmbSet:
return cmb
return None
@@ -1401,7 +1419,7 @@ def buildInputVectors(
continue
if depthCutoff is not None and len(combo.pairs) > depthCutoff:
continue
- if ignoreSliders & set([i.name for i in combo.getSliders()]):
+ if ignoreSliders & {i.name for i in combo.getSliders()}:
# if the combo's sliders are in ignoreSliders
continue
@@ -1583,7 +1601,7 @@ def buildSplitterList(self, foList):
# Dict of {item : falloff}
splitBy = {}
- for item, foSet in six.iteritems(splitBySet):
+ for item, foSet in splitBySet.items():
# Because I can only split an item once on an axis
# Assume that the foList is in priority order
# So get the min-indexed falloff in the set
@@ -1635,7 +1653,6 @@ def split(self, pBar=None):
# Meaning that splittable progs only contain splittable shapes. And splittable progs
# are only controlled by splittable controllers
for fo in self.falloffs:
-
controllers = self.sliders + self.combos + self.traversals
for ctrl in controllers:
prog = ctrl.prog
@@ -1644,7 +1661,7 @@ def split(self, pBar=None):
cSplit = fo.canRename(ctrl)
sSplit = [s for s in prog.getShapes() if not s.isRest]
sSplit = [fo.canRename(shape) for shape in sSplit]
- sSplitSame = all([i == sSplit[0] for i in sSplit])
+ sSplitSame = all(i == sSplit[0] for i in sSplit)
sSplit = sSplit[0]
if not sSplitSame:
shapes = [i.name for i in prog.getShapes()]
@@ -1673,7 +1690,7 @@ def split(self, pBar=None):
for fo in splitSmpx.falloffs:
foByAxis.setdefault(fo.axis.lower(), []).append(fo)
- for axis, foList in six.iteritems(foByAxis):
+ for axis, foList in foByAxis.items():
if pBar is not None:
pBar.setLabelText("Splitting On {0} axis".format(axis))
QApplication.processEvents()
diff --git a/scripts/simplexui/items/slider.py b/src/python/simplexui/items/slider.py
similarity index 95%
rename from scripts/simplexui/items/slider.py
rename to src/python/simplexui/items/slider.py
index 0882ed53..f49bf626 100644
--- a/scripts/simplexui/items/slider.py
+++ b/src/python/simplexui/items/slider.py
@@ -16,8 +16,6 @@
# along with Simplex. If not, see .
# pylint:disable=missing-docstring,unused-argument,no-self-use
-from __future__ import absolute_import
-
import itertools
from ..interface import undoContext
@@ -55,13 +53,12 @@ class Slider(SimplexAccessor):
classDepth = 7
- def __init__(
- self, name, simplex, prog, group, color=QColor(128, 128, 128), create=True
- ):
- if group.groupType != type(self):
+ def __init__(self, name, simplex, prog, group, color=None, create=True):
+ if group.groupType is not type(self):
raise ValueError("Cannot add this slider to a combo group")
super(Slider, self).__init__(simplex)
+ color = QColor(128, 128, 128) if color is None else color
with self.stack.store(self):
self._name = name
self._thing = None
diff --git a/scripts/simplexui/items/stack.py b/src/python/simplexui/items/stack.py
similarity index 95%
rename from scripts/simplexui/items/stack.py
rename to src/python/simplexui/items/stack.py
index d613d752..75d6d25c 100644
--- a/scripts/simplexui/items/stack.py
+++ b/src/python/simplexui/items/stack.py
@@ -16,8 +16,6 @@
# along with Simplex. If not, see .
# pylint:disable=missing-docstring,unused-argument,no-self-use
-from __future__ import absolute_import
-
import copy
from collections import OrderedDict
from contextlib import contextmanager
diff --git a/scripts/simplexui/items/traversal.py b/src/python/simplexui/items/traversal.py
similarity index 94%
rename from scripts/simplexui/items/traversal.py
rename to src/python/simplexui/items/traversal.py
index 831fbd90..40a56069 100644
--- a/scripts/simplexui/items/traversal.py
+++ b/src/python/simplexui/items/traversal.py
@@ -15,9 +15,6 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-import six
-from six.moves import range, zip
-
# pylint:disable=missing-docstring,unused-argument,no-self-use
from Qt.QtGui import QColor
from ..utils import nested
@@ -123,7 +120,7 @@ def delete(self):
@staticmethod
def removeAll(pairs):
""" """
- travs = list(set([p.travPoint.traversal for p in pairs]))
+ travs = list({p.travPoint.traversal for p in pairs})
for trav in travs:
trav.removePairs(pairs)
@@ -373,12 +370,12 @@ def __init__(
endPoint,
prog,
group,
- color=QColor(128, 128, 128),
+ color=None,
):
-
super(Traversal, self).__init__(simplex)
+ color = QColor(128, 128, 128) if color is None else color
with self.stack.store(self):
- if group.groupType != type(self):
+ if group.groupType is not type(self):
raise ValueError(
"Cannot add this Traversal to a group of a different type"
)
@@ -573,7 +570,7 @@ def ranges(self):
"""
startDict = {p.slider: p.value for p in self.startPoint.pairs}
endDict = {p.slider: p.value for p in self.endPoint.pairs}
- allSliders = six.viewkeys(startDict) | six.viewkeys(endDict)
+ allSliders = startDict.keys() | endDict.keys()
rangeDict = {}
for sli in allSliders:
@@ -596,7 +593,7 @@ def buildTraversalName(ranges):
"""
static, dynamic = [], []
- for sli, rng in six.iteritems(ranges):
+ for sli, rng in ranges.items():
if rng[0] == rng[1]:
static.append(sli)
else:
@@ -735,7 +732,7 @@ def loadV2(cls, simplex, progs, data):
for cp in cmb.pairs:
rangeDict[cp.slider] = (cp.value, cp.value)
- ssli = sorted(list(rangeDict.items()), key=lambda x: x[0].name)
+ ssli = sorted((rangeDict.items()), key=lambda x: x[0].name)
startPairs, endPairs = [], []
for slider, (startVal, endVal) in ssli:
startPairs.append(TravPair(slider, startVal))
@@ -772,7 +769,7 @@ def loadV3(cls, simplex, progs, data):
startDict = dict(data["start"])
endDict = dict(data["end"])
- sliIdxs = sorted(six.viewkeys(startDict) | six.viewkeys(endDict))
+ sliIdxs = sorted(startDict.keys() | endDict.keys())
startPairs, endPairs = [], []
for idx in sliIdxs:
startPairs.append(TravPair(simplex.sliders[idx], startDict.get(idx, 0.0)))
@@ -897,7 +894,7 @@ def removePairs(self, pairs):
pairs = sPairs + ePairs
# Get all the pairs that use the selected sliders
- sliders = set([p.slider for p in pairs])
+ sliders = {p.slider for p in pairs}
sPairs = [i for i in self.startPoint.pairs if i.slider in sliders]
ePairs = [i for i in self.endPoint.pairs if i.slider in sliders]
diff --git a/scripts/simplexui/main.pyw b/src/python/simplexui/main.pyw
similarity index 100%
rename from scripts/simplexui/main.pyw
rename to src/python/simplexui/main.pyw
diff --git a/scripts/simplexui/menu/__init__.py b/src/python/simplexui/menu/__init__.py
similarity index 95%
rename from scripts/simplexui/menu/__init__.py
rename to src/python/simplexui/menu/__init__.py
index c070e361..7df9552e 100644
--- a/scripts/simplexui/menu/__init__.py
+++ b/src/python/simplexui/menu/__init__.py
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import absolute_import
-
import importlib
import os
import pkgutil
@@ -28,8 +26,6 @@
CONTEXT = os.path.basename(sys.executable)
if CONTEXT == "maya.exe":
from . import mayaPlugins as plugins
-elif CONTEXT == "XSI.exe":
- from . import xsiPlugins as plugins
else:
from . import dummyPlugins as plugins
diff --git a/scripts/simplexui/menu/dummyPlugins/__init__.py b/src/python/simplexui/menu/dummyPlugins/__init__.py
similarity index 100%
rename from scripts/simplexui/menu/dummyPlugins/__init__.py
rename to src/python/simplexui/menu/dummyPlugins/__init__.py
diff --git a/scripts/simplexui/menu/genericPlugins/__init__.py b/src/python/simplexui/menu/genericPlugins/__init__.py
similarity index 100%
rename from scripts/simplexui/menu/genericPlugins/__init__.py
rename to src/python/simplexui/menu/genericPlugins/__init__.py
diff --git a/scripts/simplexui/menu/genericPlugins/_builtins.py b/src/python/simplexui/menu/genericPlugins/_builtins.py
similarity index 93%
rename from scripts/simplexui/menu/genericPlugins/_builtins.py
rename to src/python/simplexui/menu/genericPlugins/_builtins.py
index b13e8163..8584943c 100644
--- a/scripts/simplexui/menu/genericPlugins/_builtins.py
+++ b/src/python/simplexui/menu/genericPlugins/_builtins.py
@@ -16,8 +16,6 @@
# along with Simplex. If not, see .
# pylint:disable=unused-variable
-from __future__ import absolute_import
-
from functools import partial
from ...items import Combo, ComboPair, ProgPair, Progression, Slider
@@ -68,7 +66,7 @@ def registerSliderTree(window, clickIdx, indexes, menu):
gAct.triggered.connect(partial(self.setSelectedSliderGroups, group))
setFalloffMenu = menu.addMenu("Set Falloffs")
- sliders = list(set([i for i in items if isinstance(i, Slider)]))
+ sliders = list({i for i in items if isinstance(i, Slider)})
foChecks = {}
interps = set()
@@ -81,11 +79,11 @@ def registerSliderTree(window, clickIdx, indexes, menu):
cb = QCheckBox(falloff.name, setFalloffMenu)
chk = foChecks.get(falloff)
if not chk:
- cb.setCheckState(Qt.Unchecked)
+ cb.setCheckState(Qt.CheckState.Unchecked)
elif len(chk) != len(sliders):
- cb.setCheckState(Qt.PartiallyChecked)
+ cb.setCheckState(Qt.CheckState.PartiallyChecked)
else:
- cb.setCheckState(Qt.Checked)
+ cb.setCheckState(Qt.CheckState.Checked)
cb.stateChanged.connect(partial(self.setSelectedSliderFalloff, falloff))
@@ -144,6 +142,11 @@ def registerSliderTree(window, clickIdx, indexes, menu):
# on shape/slider
extractShapeACT = menu.addAction("Extract")
extractShapeACT.triggered.connect(self.shapeExtract)
+
+ # on shape/slider
+ exportShapeACT = menu.addAction("Export Selected ...")
+ exportShapeACT.triggered.connect(self.shapeExport)
+
# on shape/slider
connectShapeACT = menu.addAction("Connect By Name")
connectShapeACT.triggered.connect(self.shapeConnect)
@@ -209,7 +212,7 @@ def registerComboTree(window, clickIdx, indexes, menu):
setSolveMenu = menu.addMenu("Set Solve Type")
solves = set()
- combos = list(set([i for i in items if isinstance(i, Combo)]))
+ combos = list({i for i in items if isinstance(i, Combo)})
for combo in combos:
solves.add(str(combo.solveType))
@@ -230,6 +233,11 @@ def registerComboTree(window, clickIdx, indexes, menu):
# combo or below
extractShapeACT = menu.addAction("Extract")
extractShapeACT.triggered.connect(self.shapeExtract)
+
+ # combo or below
+ exportShapeACT = menu.addAction("Export Selected ...")
+ exportShapeACT.triggered.connect(self.shapeExport)
+
# combo or below
connectShapeACT = menu.addAction("Connect By Name")
connectShapeACT.triggered.connect(self.shapeConnect)
diff --git a/scripts/simplexui/menu/genericPlugins/checkPossibleCombos.py b/src/python/simplexui/menu/genericPlugins/checkPossibleCombos.py
similarity index 97%
rename from scripts/simplexui/menu/genericPlugins/checkPossibleCombos.py
rename to src/python/simplexui/menu/genericPlugins/checkPossibleCombos.py
index 58f0e880..a890f550 100644
--- a/scripts/simplexui/menu/genericPlugins/checkPossibleCombos.py
+++ b/src/python/simplexui/menu/genericPlugins/checkPossibleCombos.py
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import absolute_import
-
from functools import partial
from ...comboCheckDialog import ComboCheckDialog
diff --git a/scripts/simplexui/menu/genericPlugins/exportSplit.py b/src/python/simplexui/menu/genericPlugins/exportSplit.py
similarity index 97%
rename from scripts/simplexui/menu/genericPlugins/exportSplit.py
rename to src/python/simplexui/menu/genericPlugins/exportSplit.py
index 0545cd03..bd05a97d 100644
--- a/scripts/simplexui/menu/genericPlugins/exportSplit.py
+++ b/src/python/simplexui/menu/genericPlugins/exportSplit.py
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import absolute_import
-
from functools import partial
from Qt import QtCompat
diff --git a/scripts/simplexui/menu/genericPlugins/showFalloffs.py b/src/python/simplexui/menu/genericPlugins/showFalloffs.py
similarity index 100%
rename from scripts/simplexui/menu/genericPlugins/showFalloffs.py
rename to src/python/simplexui/menu/genericPlugins/showFalloffs.py
diff --git a/scripts/simplexui/menu/genericPlugins/showTraversals.py b/src/python/simplexui/menu/genericPlugins/showTraversals.py
similarity index 100%
rename from scripts/simplexui/menu/genericPlugins/showTraversals.py
rename to src/python/simplexui/menu/genericPlugins/showTraversals.py
diff --git a/scripts/simplexui/menu/genericPlugins/simplexUvTransfer.py b/src/python/simplexui/menu/genericPlugins/simplexUvTransfer.py
similarity index 98%
rename from scripts/simplexui/menu/genericPlugins/simplexUvTransfer.py
rename to src/python/simplexui/menu/genericPlugins/simplexUvTransfer.py
index 6982a8d6..7592d8ea 100644
--- a/scripts/simplexui/menu/genericPlugins/simplexUvTransfer.py
+++ b/src/python/simplexui/menu/genericPlugins/simplexUvTransfer.py
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import absolute_import
-
import json
from ...commands.alembicCommon import buildSmpx, readSmpx
diff --git a/scripts/simplexui/menu/genericPlugins/unsubdivide.py b/src/python/simplexui/menu/genericPlugins/unsubdivide.py
similarity index 88%
rename from scripts/simplexui/menu/genericPlugins/unsubdivide.py
rename to src/python/simplexui/menu/genericPlugins/unsubdivide.py
index 369a1491..5cb77168 100644
--- a/scripts/simplexui/menu/genericPlugins/unsubdivide.py
+++ b/src/python/simplexui/menu/genericPlugins/unsubdivide.py
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import absolute_import
-
import os
from functools import partial
@@ -24,21 +22,22 @@
from Qt import QtCompat
from Qt.QtWidgets import QAction, QMessageBox, QProgressDialog
+
try:
- import imathnumpy
+ from ..commands.numpytoimath import numpyToImath
except ImportError:
- imathnumpy = None
+ numpyToImath = None
def registerTool(window, menu):
- if imathnumpy is not None:
+ if numpyToImath is not None:
exportUnsubACT = QAction("Un Subdivide Smpx ...", window)
menu.addAction(exportUnsubACT)
exportUnsubACT.triggered.connect(partial(exportUnsubInterface, window))
def exportUnsubInterface(window):
- if imathnumpy is None:
+ if numpyToImath is None:
QMessageBox.warning(
window,
"No ImathToNumpy",
@@ -59,10 +58,10 @@ def exportUnsubInterface(window):
return
if os.path.isfile(outPath):
- btns = QMessageBox.Ok | QMessageBox.Cancel
+ btns = QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel
msg = "Unsub file already exists.\n{0}\nOverwrite?".format(outPath)
response = QMessageBox.question(window, "File already exists", msg, btns)
- if not response & QMessageBox.Ok:
+ if not response & QMessageBox.StandardButton.Ok:
return
pBar = QProgressDialog("Exporting Unsubdivided smpx File", "Cancel", 0, 100, window)
diff --git a/scripts/simplexui/menu/mayaPlugins/__init__.py b/src/python/simplexui/menu/mayaPlugins/__init__.py
similarity index 100%
rename from scripts/simplexui/menu/mayaPlugins/__init__.py
rename to src/python/simplexui/menu/mayaPlugins/__init__.py
diff --git a/scripts/simplexui/menu/mayaPlugins/exportOther.py b/src/python/simplexui/menu/mayaPlugins/exportOther.py
similarity index 97%
rename from scripts/simplexui/menu/mayaPlugins/exportOther.py
rename to src/python/simplexui/menu/mayaPlugins/exportOther.py
index 1097ceef..502b98e1 100644
--- a/scripts/simplexui/menu/mayaPlugins/exportOther.py
+++ b/src/python/simplexui/menu/mayaPlugins/exportOther.py
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import absolute_import
-
from functools import partial
import maya.cmds as cmds
diff --git a/scripts/simplexui/menu/mayaPlugins/extractProgressives.py b/src/python/simplexui/menu/mayaPlugins/extractProgressives.py
similarity index 98%
rename from scripts/simplexui/menu/mayaPlugins/extractProgressives.py
rename to src/python/simplexui/menu/mayaPlugins/extractProgressives.py
index e40b90f8..e320ab89 100644
--- a/scripts/simplexui/menu/mayaPlugins/extractProgressives.py
+++ b/src/python/simplexui/menu/mayaPlugins/extractProgressives.py
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import absolute_import
-
from functools import partial
from ...interfaceModel import coerceIndexToType
diff --git a/scripts/simplexui/menu/mayaPlugins/freezeCombo.py b/src/python/simplexui/menu/mayaPlugins/freezeCombo.py
similarity index 95%
rename from scripts/simplexui/menu/mayaPlugins/freezeCombo.py
rename to src/python/simplexui/menu/mayaPlugins/freezeCombo.py
index 91dca12f..90652d57 100644
--- a/scripts/simplexui/menu/mayaPlugins/freezeCombo.py
+++ b/src/python/simplexui/menu/mayaPlugins/freezeCombo.py
@@ -13,13 +13,8 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import absolute_import
-
from functools import partial
-import six
-from six.moves import zip
-
from maya import cmds
from ...interface.mayaInterface import disconnected
@@ -78,7 +73,7 @@ def freezeCombo(combo):
# disconnect the controller from the operator
with disconnected(simplex.DCC.op) as sliderCnx:
- for shapeIdx, pp in enumerate(combo.prog.pairs):
+ for _shapeIdx, pp in enumerate(combo.prog.pairs):
tVal = pp.value
freezeShape = pp.shape
if freezeShape.isRest:
@@ -87,7 +82,7 @@ def freezeCombo(combo):
freezeShapes.append(freezeShape)
# zero all the sliders
cnx = sliderCnx[simplex.DCC.op]
- for a in six.itervalues(cnx):
+ for a in cnx.values():
cmds.setAttr(a, 0.0)
# set the combo values
@@ -248,4 +243,3 @@ def checkFrozen(combo):
# Can't use list history to get the chain because it's a pseudo-cycle
ret.extend(_getDeformerChain(cc))
return ret
-
diff --git a/scripts/simplexui/menu/mayaPlugins/generateShapeIncrementals.py b/src/python/simplexui/menu/mayaPlugins/generateShapeIncrementals.py
similarity index 97%
rename from scripts/simplexui/menu/mayaPlugins/generateShapeIncrementals.py
rename to src/python/simplexui/menu/mayaPlugins/generateShapeIncrementals.py
index 2206e108..47bfd7ab 100644
--- a/scripts/simplexui/menu/mayaPlugins/generateShapeIncrementals.py
+++ b/src/python/simplexui/menu/mayaPlugins/generateShapeIncrementals.py
@@ -15,12 +15,9 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import absolute_import
-
from functools import partial
import maya.cmds as cmds
-from six.moves import range
from ...interfaceModel import coerceIndexToType
from ...items import Combo, Slider
diff --git a/scripts/simplexui/menu/mayaPlugins/importObjs.py b/src/python/simplexui/menu/mayaPlugins/importObjs.py
similarity index 95%
rename from scripts/simplexui/menu/mayaPlugins/importObjs.py
rename to src/python/simplexui/menu/mayaPlugins/importObjs.py
index 8c49277d..4600ae19 100644
--- a/scripts/simplexui/menu/mayaPlugins/importObjs.py
+++ b/src/python/simplexui/menu/mayaPlugins/importObjs.py
@@ -1,10 +1,7 @@
-from __future__ import absolute_import
-
import os
from functools import partial
import maya.cmds as cmds
-import six
from ...items import Combo, Slider, Traversal
from Qt.QtWidgets import (
@@ -136,23 +133,23 @@ def importObjList(simplex, paths, pBar, reorder=True):
travMasters[shape.name] = master
comboDepth = {}
- for k, v in six.iteritems(comboMasters):
+ for k, v in comboMasters.items():
depth = len(v.pairs)
comboDepth.setdefault(depth, {})[k] = v
importOrder = []
- for shapeName, slider in six.iteritems(sliderMasters):
+ for shapeName, slider in sliderMasters.items():
importOrder.append(
(shapeName, slider, shapeDict[shapeName], inPairs[shapeName])
)
for depth in sorted(comboDepth.keys()):
- for shapeName, combo in six.iteritems(comboDepth[depth]):
+ for shapeName, combo in comboDepth[depth].items():
importOrder.append(
(shapeName, combo, shapeDict[shapeName], inPairs[shapeName])
)
- for shapeName, trav in six.iteritems(travMasters):
+ for shapeName, trav in travMasters.items():
importOrder.append((shapeName, trav, shapeDict[shapeName], inPairs[shapeName]))
if pBar is not None:
diff --git a/scripts/simplexui/menu/mayaPlugins/linearizeTraversal.py b/src/python/simplexui/menu/mayaPlugins/linearizeTraversal.py
similarity index 97%
rename from scripts/simplexui/menu/mayaPlugins/linearizeTraversal.py
rename to src/python/simplexui/menu/mayaPlugins/linearizeTraversal.py
index 3605c846..dab8823e 100644
--- a/scripts/simplexui/menu/mayaPlugins/linearizeTraversal.py
+++ b/src/python/simplexui/menu/mayaPlugins/linearizeTraversal.py
@@ -15,7 +15,6 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import absolute_import
from functools import partial
import maya.cmds as cmds
from Qt.QtWidgets import QAction
diff --git a/scripts/simplexui/menu/mayaPlugins/makeShelfBtn.py b/src/python/simplexui/menu/mayaPlugins/makeShelfBtn.py
similarity index 95%
rename from scripts/simplexui/menu/mayaPlugins/makeShelfBtn.py
rename to src/python/simplexui/menu/mayaPlugins/makeShelfBtn.py
index 44f97be8..b682376f 100644
--- a/scripts/simplexui/menu/mayaPlugins/makeShelfBtn.py
+++ b/src/python/simplexui/menu/mayaPlugins/makeShelfBtn.py
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import absolute_import
-
import os
from Qt.QtWidgets import QAction
@@ -53,9 +51,7 @@
simplexui.runSimplexUI()
sys.path.pop(0)
-""".format(
- dn(dn(dn(dn(__file__))))
-)
+""".format(dn(dn(dn(dn(__file__)))))
def registerTool(window, menu):
diff --git a/scripts/simplexui/menu/mayaPlugins/relaxToSelection.py b/src/python/simplexui/menu/mayaPlugins/relaxToSelection.py
similarity index 98%
rename from scripts/simplexui/menu/mayaPlugins/relaxToSelection.py
rename to src/python/simplexui/menu/mayaPlugins/relaxToSelection.py
index 30b4b5d0..16727300 100644
--- a/scripts/simplexui/menu/mayaPlugins/relaxToSelection.py
+++ b/src/python/simplexui/menu/mayaPlugins/relaxToSelection.py
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import absolute_import
-
import maya.cmds as cmds
from Qt.QtWidgets import QAction
diff --git a/scripts/simplexui/menu/mayaPlugins/reloadDefinition.py b/src/python/simplexui/menu/mayaPlugins/reloadDefinition.py
similarity index 96%
rename from scripts/simplexui/menu/mayaPlugins/reloadDefinition.py
rename to src/python/simplexui/menu/mayaPlugins/reloadDefinition.py
index d8ad944a..5172228b 100644
--- a/scripts/simplexui/menu/mayaPlugins/reloadDefinition.py
+++ b/src/python/simplexui/menu/mayaPlugins/reloadDefinition.py
@@ -15,7 +15,6 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import absolute_import
from functools import partial
from Qt.QtWidgets import QAction
diff --git a/scripts/simplexui/menu/mayaPlugins/snapToNeutral.py b/src/python/simplexui/menu/mayaPlugins/snapToNeutral.py
similarity index 98%
rename from scripts/simplexui/menu/mayaPlugins/snapToNeutral.py
rename to src/python/simplexui/menu/mayaPlugins/snapToNeutral.py
index a6e8d1f9..5ee142e6 100644
--- a/scripts/simplexui/menu/mayaPlugins/snapToNeutral.py
+++ b/src/python/simplexui/menu/mayaPlugins/snapToNeutral.py
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import absolute_import
-
from functools import partial
import maya.cmds as cmds
diff --git a/scripts/simplexui/menu/mayaPlugins/softSelectToCluster.py b/src/python/simplexui/menu/mayaPlugins/softSelectToCluster.py
similarity index 97%
rename from scripts/simplexui/menu/mayaPlugins/softSelectToCluster.py
rename to src/python/simplexui/menu/mayaPlugins/softSelectToCluster.py
index 54cecdc5..c3ff2d2a 100644
--- a/scripts/simplexui/menu/mayaPlugins/softSelectToCluster.py
+++ b/src/python/simplexui/menu/mayaPlugins/softSelectToCluster.py
@@ -15,11 +15,8 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import absolute_import, print_function
-
import maya.cmds as cmds
import maya.OpenMaya as om
-from six.moves import range
from Qt.QtWidgets import QAction
@@ -33,7 +30,7 @@ def registerTool(window, menu):
def softSelectToClusterInterface():
sel = cmds.ls(sl=True, objectsOnly=True)
if sel:
- name = sel[0].split('|')[-1]
+ name = sel[0].split("|")[-1]
softSelectToCluster(sel[0], "{0}_Soft".format(name))
@@ -148,7 +145,7 @@ def getSoftSelectionValues(myNode, returnSimpleIndices=True):
elif ctyp == om.MFn.kLatticeComponent:
div_s = cmds.getAttr(depNode_name + ".sDivisions")
div_t = cmds.getAttr(depNode_name + ".tDivisions")
- div_u = cmds.getAttr(depNode_name + ".uDivisions")
+ # div_u = cmds.getAttr(depNode_name + ".uDivisions")
tripleFn = om.MFnTripleIndexedComponent(component)
for i in range(count):
@@ -201,4 +198,3 @@ def softSelectToCluster(tfm, name):
cmds.xform(clusterHandle, absolute=True, worldSpace=True, pivots=pos)
clusterShape = cmds.listRelatives(clusterHandle, children=True, shapes=True)
cmds.setAttr(clusterShape[0] + ".origin", *pos)
-
diff --git a/scripts/simplexui/menu/mayaPlugins/tweakMix.py b/src/python/simplexui/menu/mayaPlugins/tweakMix.py
similarity index 96%
rename from scripts/simplexui/menu/mayaPlugins/tweakMix.py
rename to src/python/simplexui/menu/mayaPlugins/tweakMix.py
index fb7b3bec..991a59fa 100644
--- a/scripts/simplexui/menu/mayaPlugins/tweakMix.py
+++ b/src/python/simplexui/menu/mayaPlugins/tweakMix.py
@@ -15,13 +15,9 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import absolute_import
-
from functools import partial
import maya.cmds as cmds
-import six
-from six.moves import range, zip
from ...interface.mayaInterface import disconnected
from ...interfaceModel import coerceIndexToType
@@ -93,7 +89,7 @@ def tweakMix(simplex, combos, live):
# disconnect any float shapes
with disconnected(floatShapes):
cnx = sliderCnx[simplex.DCC.op]
- for a in six.itervalues(cnx):
+ for a in cnx.values():
cmds.setAttr(a, 0.0)
# set the combo values
@@ -108,7 +104,7 @@ def tweakMix(simplex, combos, live):
tweakMeshes = []
with disconnected(simplex.DCC.shapeNode) as shapeCnx:
cnx = shapeCnx[simplex.DCC.shapeNode]
- for a in six.itervalues(cnx):
+ for a in cnx.values():
cmds.setAttr(a, 0.0)
for tshape, shapeVal in tweakShapes:
cmds.setAttr(tshape.thing, shapeVal)
diff --git a/scripts/simplexui/menu/mayaPlugins/updateRestShape.py b/src/python/simplexui/menu/mayaPlugins/updateRestShape.py
similarity index 96%
rename from scripts/simplexui/menu/mayaPlugins/updateRestShape.py
rename to src/python/simplexui/menu/mayaPlugins/updateRestShape.py
index ea951cd7..00ba7cd1 100644
--- a/scripts/simplexui/menu/mayaPlugins/updateRestShape.py
+++ b/src/python/simplexui/menu/mayaPlugins/updateRestShape.py
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import absolute_import
-
import textwrap
from functools import partial
@@ -64,9 +62,9 @@ def updateRestShapeInterface(window):
"Continue anyway?",
]
msg = "\n".join(msg)
- btns = QMessageBox.Ok | QMessageBox.Cancel
+ btns = QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel
bret = QMessageBox.question(window, "Live Connections", msg, btns)
- if not bret & QMessageBox.Ok:
+ if not bret & QMessageBox.StandardButton.Ok:
return
updateRestShape(mesh, sel, window=window)
diff --git a/scripts/simplexui/simplexDialog.py b/src/python/simplexui/simplexDialog.py
similarity index 91%
rename from scripts/simplexui/simplexDialog.py
rename to src/python/simplexui/simplexDialog.py
index 2e0b6654..0ea7c44e 100644
--- a/scripts/simplexui/simplexDialog.py
+++ b/src/python/simplexui/simplexDialog.py
@@ -19,8 +19,6 @@
# Ignore a bunch of linter warnings that show up because of my choice of abstraction
# pylint: disable=unused-argument,too-many-public-methods,relative-import
# pylint: disable=too-many-statements,no-self-use,missing-docstring
-from __future__ import absolute_import
-
import json
import os
import re
@@ -28,9 +26,6 @@
import weakref
from contextlib import contextmanager
-import six
-from six.moves import range, zip
-
from .comboCheckDialog import ComboCheckDialog
from .falloffDialog import FalloffDialog
from .interface import DCC
@@ -474,7 +469,9 @@ def unifySliderSelection(self):
an item on the slider tree is selected
"""
mods = QApplication.keyboardModifiers()
- if not mods & (Qt.ControlModifier | Qt.ShiftModifier):
+ if not mods & (
+ Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.ShiftModifier
+ ):
comboSelModel = self.uiComboTREE.selectionModel()
if not comboSelModel:
return
@@ -488,7 +485,9 @@ def unifyComboSelection(self):
an item on the combo tree is selected
"""
mods = QApplication.keyboardModifiers()
- if not mods & (Qt.ControlModifier | Qt.ShiftModifier):
+ if not mods & (
+ Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.ShiftModifier
+ ):
sliderSelModel = self.uiSliderTREE.selectionModel()
if not sliderSelModel:
return
@@ -526,7 +525,9 @@ def enableComboRequirements(self):
depCheck = self.uiComboDependGRP.isChecked()
comboModel.filterRequiresAll = self.uiComboDependAllRDO.isChecked() and depCheck
comboModel.filterRequiresAny = self.uiComboDependAnyRDO.isChecked() and depCheck
- comboModel.filterRequiresOnly = self.uiComboDependOnlyRDO.isChecked() and depCheck
+ comboModel.filterRequiresOnly = (
+ self.uiComboDependOnlyRDO.isChecked() and depCheck
+ )
comboModel.invalidateFilter()
def populateSliderRequirements(self):
@@ -552,8 +553,12 @@ def enableSliderRequirements(self):
return
depCheck = self.uiSliderDependGRP.isChecked()
- sliderModel.filterRequiresAny = self.uiSliderDependAnyRDO.isChecked() and depCheck
- sliderModel.filterRequiresAll = self.uiSliderDependAllRDO.isChecked() and depCheck
+ sliderModel.filterRequiresAny = (
+ self.uiSliderDependAnyRDO.isChecked() and depCheck
+ )
+ sliderModel.filterRequiresAll = (
+ self.uiSliderDependAllRDO.isChecked() and depCheck
+ )
sliderModel.invalidateFilter()
# Bottom Left Corner Buttons
@@ -597,7 +602,7 @@ def autoSetSliders(self):
self.simplex.setSlidersWeights(sliders, weights)
self.uiSliderTREE.repaint()
- def _getAName(self, tpe, default=None, taken=tuple(), uniqueAccept=False):
+ def _getAName(self, tpe, default=None, taken=(), uniqueAccept=False):
"""uniqueAccept forces the user to provide and accept a unique name
If the user enters a non-unique name, the name is uniquified, and the
dialog is re-shown with the unique name as the default suggestion
@@ -829,9 +834,7 @@ def shapeConnectScene(self):
pairDict[pp.shape.name] = pp
# get all common names
- selKeys = set(six.iterkeys(selDict))
- pairKeys = set(six.iterkeys(pairDict))
- common = selKeys & pairKeys
+ common = selDict.keys() & pairDict.keys()
# get those items
pairs = [pairDict[i] for i in common]
@@ -870,6 +873,72 @@ def shapeExtract(self):
comboIdxs = self.uiComboTREE.getSelectedIndexes()
return self.shapeIndexExtract(sliderIdxs + comboIdxs)
+ def shapeExport(self):
+ """Export meshes"""
+ sliderIdxs = self.uiSliderTREE.getSelectedIndexes()
+ comboIdxs = self.uiComboTREE.getSelectedIndexes()
+ return self.shapeIndexExport(sliderIdxs + comboIdxs)
+
+ def shapeIndexExport(self, indexes):
+ """Export meshes
+
+ Parameters
+ ----------
+ indexes : [QModelIndex, ...]
+ A list of indexes to extract
+ """
+ # Importing this here to keep people from getting confused
+ # You should use self._fileDialog which does compatibility
+ # stuff for file saving. Since I'm looking for a folder here
+ # I don't need to do that
+ from Qt.QtWidgets import QFileDialog
+
+ if self.simplex is None:
+ return
+
+ pref = Prefs()
+ defaultPath = str(
+ pref.restoreProperty("systemExportFolder", os.path.join(os.path.expanduser("~")))
+ )
+ outfld = QFileDialog.getExistingDirectory(
+ self, "Pick Export Folder", defaultPath
+ )
+ if not outfld:
+ return
+
+ pref.recordProperty("systemExportFolder", outfld)
+ pref.save()
+
+ pairs = coerceIndexToChildType(indexes, ProgPair)
+ pairs = [i.model().itemFromIndex(i) for i in pairs]
+ pairs = makeUnique([i for i in pairs if not i.shape.isRest])
+ pairs.sort(key=lambda x: naturalSortKey(x.shape.name))
+
+ # Set up the progress bar
+ pBar = QProgressDialog("Exporting Shapes", "Cancel", 0, 100, self)
+ pBar.setMaximum(len(pairs))
+
+ extension = '.abc'
+ # Do the extractions
+ for pair in pairs:
+ c = pair.prog.controller
+ extracted = c.extractShape(pair.shape, live=False)
+ path = os.path.join(outfld, c.name + extension)
+ if os.path.exists(path):
+ os.remove(path)
+
+ self.simplex.DCC.exportMesh(extracted, path)
+ self.simplex.DCC.deleteObj(extracted)
+
+ # ProgressBar
+ pBar.setValue(pBar.value() + 1)
+ pBar.setLabelText("Extracting:\n{0}".format(pair.shape.name))
+ QApplication.processEvents()
+ if pBar.wasCanceled():
+ return
+
+ pBar.close()
+
def shapeIndexExtract(self, indexes, live=None):
"""Create meshes that are possibly live-connected to the shapes
@@ -1130,13 +1199,9 @@ def importSystemFromFile(self):
pref = Prefs()
defaultPath = str(
- pref.restoreProperty(
- "systemImport", os.path.join(os.path.expanduser("~"))
- )
- )
- path = self._fileDialog(
- "Import Template", defaultPath, impTypes, save=False
+ pref.restoreProperty("systemImport", os.path.join(os.path.expanduser("~")))
)
+ path = self._fileDialog("Import Template", defaultPath, impTypes, save=False)
if not path:
return
pref.recordProperty("systemImport", os.path.dirname(path))
@@ -1219,9 +1284,7 @@ def exportSystemTemplate(self):
pref = Prefs()
defaultPath = str(
- pref.restoreProperty(
- "systemExport", os.path.join(os.path.expanduser("~"))
- )
+ pref.restoreProperty("systemExport", os.path.join(os.path.expanduser("~")))
)
path = self._fileDialog(
"Export Template", defaultPath, ["smpx", "json"], save=True
@@ -1274,7 +1337,7 @@ def setSelectedSliderFalloff(self, falloff, state):
return
sliders = self.uiSliderTREE.getSelectedItems(Slider)
for s in sliders:
- if state == Qt.Checked:
+ if state == Qt.CheckState.Checked:
s.prog.addFalloff(falloff)
else:
s.prog.removeFalloff(falloff)
diff --git a/scripts/simplexui/travCheckDialog.py b/src/python/simplexui/travCheckDialog.py
similarity index 88%
rename from scripts/simplexui/travCheckDialog.py
rename to src/python/simplexui/travCheckDialog.py
index 055b21ff..8168cb2a 100644
--- a/scripts/simplexui/travCheckDialog.py
+++ b/src/python/simplexui/travCheckDialog.py
@@ -15,13 +15,8 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex. If not, see .
-from __future__ import absolute_import
-
from itertools import combinations, product
-import six
-from six.moves import range, zip
-
from .dragFilter import DragFilter
from .items import Slider, Traversal
from Qt import QtCompat
@@ -111,7 +106,7 @@ def buildPossibleTraversals(
sls = trav.allSliders()
if all(r in sliders for r in sls):
rngs = trav.ranges()
- key = frozenset([(k.name, v) for k, v in six.iteritems(rngs)])
+ key = frozenset([(k.name, v) for k, v in rngs.items()])
onlys[key] = trav
toAdd = []
@@ -144,9 +139,9 @@ def __init__(self, pairs, trav, *args, **kwargs):
for slider, rng in pairs:
item = QTreeWidgetItem(self)
- item.setData(0, Qt.EditRole, slider.name)
- item.setData(1, Qt.EditRole, rng[0])
- item.setData(2, Qt.EditRole, rng[1])
+ item.setData(0, Qt.ItemDataRole.EditRole, slider.name)
+ item.setData(1, Qt.ItemDataRole.EditRole, rng[0])
+ item.setData(2, Qt.ItemDataRole.EditRole, rng[1])
if exists:
item.setForeground(0, grayBrush)
item.setForeground(1, grayBrush)
@@ -198,7 +193,12 @@ def __init__(
self.parUI = parent
self.gparUI = grandparent
self.maxPoss = 100
- self.colCheckRoles = [Qt.UserRole, Qt.UserRole, Qt.UserRole, Qt.EditRole]
+ self.colCheckRoles = [
+ Qt.ItemDataRole.UserRole,
+ Qt.ItemDataRole.UserRole,
+ Qt.ItemDataRole.UserRole,
+ Qt.ItemDataRole.EditRole,
+ ]
self.uiCreateSelectedBTN.clicked.connect(self.createMissing)
self.uiMinLimitSPIN.valueChanged.connect(self.populateWithoutUpdate)
@@ -219,7 +219,7 @@ def __init__(
self.dynDict = dynamics or {}
self.setSliders(sliders)
if self.mode == "create":
- self.uiAutoUpdateCHK.setCheckState(Qt.Unchecked)
+ self.uiAutoUpdateCHK.setCheckState(Qt.CheckState.Unchecked)
self.uiAutoUpdateCHK.hide()
self.uiManualUpdateBTN.hide()
@@ -247,12 +247,12 @@ def dragTick(self, ticks, mul):
"""
items = self.uiEditTREE.selectedItems()
for item in items:
- val = item.data(3, Qt.EditRole)
+ val = item.data(3, Qt.ItemDataRole.EditRole)
val += (0.05) * ticks * mul
if abs(val) < 1.0e-5:
val = 0.0
val = max(min(val, 1.0), -1.0)
- item.setData(3, Qt.EditRole, val)
+ item.setData(3, Qt.ItemDataRole.EditRole, val)
self.uiEditTREE.viewport().update()
def setSliders(self, val):
@@ -272,18 +272,22 @@ def setSliders(self, val):
val = val or []
for slider in val:
item = QTreeWidgetItem(self.uiEditTREE, [slider.name])
- item.setFlags(item.flags() | Qt.ItemIsEditable)
+ item.setFlags(item.flags() | Qt.ItemFlag.ItemIsEditable)
rangeVals = self.valueDict.get(slider, [-1.0, 1.0])
- item.setData(0, Qt.UserRole, slider)
+ item.setData(0, Qt.ItemDataRole.UserRole, slider)
for col in range(1, 3):
val = dvs[col]
item.setData(col, self.colCheckRoles[col], val)
rng = slider.prog.getRange()
if val in rng:
- chk = Qt.Checked if val in rangeVals else Qt.Unchecked
+ chk = (
+ Qt.CheckState.Checked
+ if val in rangeVals
+ else Qt.CheckState.Unchecked
+ )
item.setCheckState(col, chk)
- item.setCheckState(3, self.dynDict.get(slider, Qt.Checked))
+ item.setCheckState(3, self.dynDict.get(slider, Qt.CheckState.Checked))
for col in reversed(list(range(4))):
self.uiEditTREE.resizeColumnToContents(col)
@@ -321,15 +325,15 @@ def _populate(self):
for row in range(root.childCount()):
item = root.child(row)
- slider = item.data(0, Qt.UserRole)
+ slider = item.data(0, Qt.ItemDataRole.UserRole)
if slider is not None:
sliderList.append(slider)
lv = [
item.data(col, self.colCheckRoles[col])
for col in range(1, 3)
- if item.checkState(col) == Qt.Checked
+ if item.checkState(col) == Qt.CheckState.Checked
]
- dyn = item.checkState(3) == Qt.Checked
+ dyn = item.checkState(3) == Qt.CheckState.Checked
lockDict[slider] = (lv, dyn)
tooMany, toAdd = buildPossibleTraversals(
diff --git a/scripts/simplexui/traversalDialog.py b/src/python/simplexui/traversalDialog.py
similarity index 95%
rename from scripts/simplexui/traversalDialog.py
rename to src/python/simplexui/traversalDialog.py
index e97433bf..369038bd 100644
--- a/scripts/simplexui/traversalDialog.py
+++ b/src/python/simplexui/traversalDialog.py
@@ -19,12 +19,8 @@
# Ignore a bunch of linter warnings that show up because of my choice of abstraction
# pylint: disable=unused-argument,too-many-public-methods,relative-import
# pylint: disable=too-many-statements,no-self-use,missing-docstring
-from __future__ import absolute_import
-
import re
-import six
-
from .interface import DCC
from .interfaceModel import (
TraversalFilterModel,
@@ -251,9 +247,7 @@ def shapeConnectFromSelection(self):
pairDict[pp.shape.name] = pp
# get all common names
- selKeys = set(six.iterkeys(selDict))
- pairKeys = set(six.iterkeys(pairDict))
- common = selKeys & pairKeys
+ common = selDict.keys() & pairDict.keys()
# get those items
pairs = [pairDict[i] for i in common]
diff --git a/scripts/simplexui/ui/comboCheckDialog.ui b/src/python/simplexui/ui/comboCheckDialog.ui
similarity index 100%
rename from scripts/simplexui/ui/comboCheckDialog.ui
rename to src/python/simplexui/ui/comboCheckDialog.ui
diff --git a/scripts/simplexui/ui/falloffDialog.ui b/src/python/simplexui/ui/falloffDialog.ui
similarity index 100%
rename from scripts/simplexui/ui/falloffDialog.ui
rename to src/python/simplexui/ui/falloffDialog.ui
diff --git a/scripts/simplexui/ui/simplexDialog.ui b/src/python/simplexui/ui/simplexDialog.ui
similarity index 100%
rename from scripts/simplexui/ui/simplexDialog.ui
rename to src/python/simplexui/ui/simplexDialog.ui
diff --git a/scripts/simplexui/ui/travCheckDialog.ui b/src/python/simplexui/ui/travCheckDialog.ui
similarity index 100%
rename from scripts/simplexui/ui/travCheckDialog.ui
rename to src/python/simplexui/ui/travCheckDialog.ui
diff --git a/scripts/simplexui/ui/traversalDialog.ui b/src/python/simplexui/ui/traversalDialog.ui
similarity index 100%
rename from scripts/simplexui/ui/traversalDialog.ui
rename to src/python/simplexui/ui/traversalDialog.ui
diff --git a/scripts/simplexui/utils.py b/src/python/simplexui/utils.py
similarity index 95%
rename from scripts/simplexui/utils.py
rename to src/python/simplexui/utils.py
index e49b3044..8cae2ab8 100755
--- a/scripts/simplexui/utils.py
+++ b/src/python/simplexui/utils.py
@@ -16,7 +16,6 @@
# along with Simplex. If not, see .
"""Utility functions."""
-from __future__ import absolute_import
import os
import re
@@ -293,9 +292,11 @@ def getIcon(iconName):
class Prefs(object):
"""A wrapper for reading/writing prefs both internal and external to blur"""
+
def __init__(self):
if AT_BLUR:
import blurdev.prefs
+
self._pref = blurdev.prefs.find("tools/simplex3")
else:
self._pref = QSettings("Blur", "Simplex3")
@@ -317,4 +318,3 @@ def save(self):
self._pref.save()
else:
self._pref.sync()
-
diff --git a/subprojects/maya/meson.build b/subprojects/maya/meson.build
index 65df36ef..1d6dc7df 100644
--- a/subprojects/maya/meson.build
+++ b/subprojects/maya/meson.build
@@ -16,8 +16,14 @@ maya_link_args = []
if os_name == 'windows'
maya_install_base = 'c:/Program Files/Autodesk'
maya_plugin_ext = 'mll'
- maya_compile_args += ['-DNT_PLUGIN']
- maya_link_args = ['/export:initializePlugin', '/export:uninitializePlugin']
+ maya_compile_args += ['-DNT_PLUGIN', '/Zc:__cplusplus']
+ maya_link_args = [
+ # Export the methods that maya requires
+ '/export:initializePlugin',
+ '/export:uninitializePlugin',
+ # Make sure to look for pdb file in the same directory as the .mll
+ '/PDBALTPATH:%_PDB%',
+ ]
elif os_name == 'darwin'
maya_install_base = '/Applications/Autodesk'
maya_plugin_ext = 'bundle'
@@ -31,8 +37,7 @@ elif os_name == 'darwin'
maya_compile_args += ['-arch', 'x86_64']
maya_link_args += ['-arch', 'x86_64']
if maya_version.version_compare('>=2024')
- # clang will build for both x86 and arm
- # if both arches are in the command line args
+ # build both the arm and x86 plugins when compiling for mac
maya_compile_args += ['-arch', 'arm64']
maya_link_args += ['-arch', 'arm64']
endif
@@ -54,13 +59,16 @@ if maya_devkit_base != ''
maya_install_path = maya_devkit_base
endif
+includes = []
maya_inc_dir = maya_install_path / maya_inc_suffix
message('Searching Maya Include directory:', maya_inc_dir)
-maya_inc = include_directories(maya_inc_dir)
+includes += include_directories(maya_inc_dir)
maya_lib_dir = maya_install_path / maya_lib_suffix
message('Searching Maya lib directory:', maya_lib_dir)
+maya_bin_dir = maya_install_path / 'bin'
+
# Get all the maya libraries
cmplr = meson.get_compiler('cpp')
maya_libs = [
@@ -74,31 +82,73 @@ maya_libs = [
]
# Link to maya's qt libs if required
-# This doesn't do MOC stuff ... yet
+# Below, I expose the bin and include dirs so I can directly invoke the
+# moc exe included with maya. Saves from having to make a maya qt module
+
+qt_moc_path = ''
if maya_link_qt
fs = import('fs')
- if not fs.is_dir(maya_inc_dir / 'QtCore')
- error(
- 'Could not find Maya QT headers with `maya_link_qt` defined\n',
- 'You probably need to unzip `include/qt_*-include.zip`\n',
- 'Checking in folder: ', maya_inc_dir,
- )
+
+ if maya_version.version_compare('>=2025')
+ if not fs.is_dir(maya_install_path / 'Qt' / 'include' / 'QtCore')
+ error(
+ 'Could not find Maya QT headers with `maya_link_qt` defined\n',
+ 'You probably need to unzip `devkitBase/Qt.zip`\n',
+ 'Checking in folder: ', maya_install_path,
+ )
+ endif
+ maya_qt_lib_names = [f'Qt6Core', f'Qt6Gui', f'Qt6Widgets']
+
+ qt_lib_dir = maya_lib_dir
+ qt_moc_path = maya_bin_dir / 'moc'
+ if maya_devkit_base != ''
+ qt_lib_dir = maya_devkit_base / 'Qt' / 'lib'
+
+ mocdirs = [
+ maya_devkit_base / 'Qt' / 'bin',
+ maya_devkit_base / 'Qt' / 'libexec',
+ ]
+ moc_prg = find_program('moc', dirs : mocdirs, required : true)
+ qt_moc_path = moc_prg.full_path()
+
+ includes += include_directories(maya_devkit_base / 'Qt' / 'include')
+ endif
+ else
+ if not fs.is_dir(maya_inc_dir / 'QtCore')
+ error(
+ 'Could not find Maya QT headers with `maya_link_qt` defined\n',
+ 'You probably need to unzip `include/qt_*-include.zip`\n',
+ 'Checking in folder: ', maya_inc_dir,
+ )
+ endif
+ qt_ver = 5
+ maya_qt_lib_names = [f'Qt5Core', f'Qt5Gui', f'Qt5Widgets']
+ qt_lib_dir = maya_lib_dir
+ qt_moc_path = maya_bin_dir / 'moc'
+ if maya_devkit_base != ''
+ qt_moc_path = maya_devkit_base / 'devkit' / 'bin' / 'moc'
+ endif
endif
- maya_qt_lib_names = ['Qt5Core', 'Qt5Gui', 'Qt5Widgets']
if maya_qt_extra_includes != ''
maya_qt_lib_names += maya_qt_extra_includes.split(';')
endif
-
foreach lib_name : maya_qt_lib_names
- maya_libs += cmplr.find_library(lib_name, dirs : maya_lib_dir)
+ maya_libs += cmplr.find_library(lib_name, dirs : qt_lib_dir)
endforeach
+
endif
maya_dep = declare_dependency(
dependencies : maya_libs,
- include_directories : maya_inc,
- variables : {'name_suffix' : maya_plugin_ext, 'maya_version' : maya_version},
+ include_directories : includes,
+ variables : {
+ 'name_suffix' : maya_plugin_ext,
+ 'maya_version' : maya_version,
+ 'maya_bin_dir': maya_bin_dir,
+ 'maya_inc_dir': maya_inc_dir,
+ 'qt_moc_path': qt_moc_path,
+ },
compile_args : maya_compile_args,
link_args : maya_link_args,
)
diff --git a/subprojects/packagefiles/rapidjson/meson.build b/subprojects/packagefiles/rapidjson/meson.build
new file mode 100644
index 00000000..5a58fcdb
--- /dev/null
+++ b/subprojects/packagefiles/rapidjson/meson.build
@@ -0,0 +1,4 @@
+project('rapidjson', 'cpp', version: '1.1.0', license: 'MIT')
+
+rapidjson_inc = include_directories('include')
+rapidjson_dep = declare_dependency(include_directories: rapidjson_inc)
diff --git a/subprojects/rapidjson.wrap b/subprojects/rapidjson.wrap
index 50b83466..a6072631 100644
--- a/subprojects/rapidjson.wrap
+++ b/subprojects/rapidjson.wrap
@@ -1,13 +1,8 @@
-[wrap-file]
-directory = rapidjson-1.1.0
-source_url = https://github.com/Tencent/rapidjson/archive/v1.1.0.tar.gz
-source_filename = rapidjson-1.1.0.tar.gz
-source_hash = bf7ced29704a1e696fbccf2a2b4ea068e7774fa37f6d7dd4039d0787f8bed98e
-patch_filename = rapidjson_1.1.0-2_patch.zip
-patch_url = https://wrapdb.mesonbuild.com/v2/rapidjson_1.1.0-2/get_patch
-patch_hash = c1480d0ecef09dbaa4b4d85d86090205386fb2c7e87f4f158b20dbbda14c9afc
-source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/rapidjson_1.1.0-2/rapidjson-1.1.0.tar.gz
-wrapdb_version = 1.1.0-2
+[wrap-git]
+url = https://github.com/Tencent/rapidjson.git
+revision = 24b5e7a8b27f42fa16b96fc70aade9106cf7102f
+
+patch_directory = rapidjson
[provide]
rapidjson = rapidjson_dep
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 00000000..30d85ba4
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,55 @@
+[tox]
+envlist =
+ compat-py37
+ qtcompat-py37
+ format
+ lint
+
+skip_missing_interpreters = True
+requires = tox>4.0
+
+[testenv]
+package = editable
+usedevelop = True
+changedir = {toxinidir}
+passenv = USERNAME
+deps =
+ pip-tools
+ Qt.py==1.4.7
+
+[testenv:compat-py37]
+deps =
+commands =
+ python -m compileall -f -q -x "\.tox|shared-venv|subprojects" .
+
+[testenv:.qtcompat]
+deps =
+ numpy==1.21.6
+ PySide2
+
+[testenv:qtcompat-py37]
+deps = {[testenv:.qtcompat]deps}
+commands =
+ python {envsitepackagesdir}/Qt_convert_enum.py . --check
+ python {envsitepackagesdir}/Qt_convert_enum.py . --partial
+
+[testenv:qtcompat-py37-fix]
+deps = {[testenv:.qtcompat]deps}
+commands =
+ python {envsitepackagesdir}/Qt_convert_enum.py . --check --write
+ python {envsitepackagesdir}/Qt_convert_enum.py . --partial
+
+[testenv:setup]
+deps =
+ build
+commands =
+ # Ensure the version.py file is created
+ python -m build --version
+
+[testenv:lint]
+deps = ruff
+commands = ruff check --extend-exclude=tests/ .
+
+[testenv:format]
+deps = ruff
+commands = ruff format --check .