Skip to content

Commit

Permalink
Merge pull request #8 from theRussetPotato/dev
Browse files Browse the repository at this point in the history
Massive code refactor and new features
  • Loading branch information
theRussetPotato committed Jan 5, 2022
2 parents 7e3a4f1 + 8ae3534 commit 327de2d
Show file tree
Hide file tree
Showing 32 changed files with 3,019 additions and 2,106 deletions.
72 changes: 72 additions & 0 deletions .github/workflows/maya_unittests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Weights Editor

# Run tests on any push or pullRequest event
on: [push, pull_request]

jobs:

maya2018:
runs-on: ubuntu-latest

steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Run Unittests
uses: docker://mottosso/maya:2018 # For all available Maya versions, see https://github.com/mottosso/docker-maya
with:
args: /usr/autodesk/maya/bin/mayapy -m unittest discover -s ./scripts/weights_editor_tool/tests -v

maya2019:
runs-on: ubuntu-latest

steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Load Docker Image
uses: docker://mottosso/maya:2019 # For all available Maya versions, see https://github.com/mottosso/docker-maya
with:
args: /usr/autodesk/maya/bin/mayapy -m unittest discover -s ./scripts/weights_editor_tool/tests -v

maya2020:
runs-on: ubuntu-latest

steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Run Unittests
uses: docker://mottosso/maya:2020 # For all available Maya versions, see https://github.com/mottosso/docker-maya
with:
args: /usr/autodesk/maya/bin/mayapy -m unittest discover -s ./scripts/weights_editor_tool/tests -v

maya2020sp1:
runs-on: ubuntu-latest

steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Load Docker Image
uses: docker://mottosso/maya:2020sp1 # For all available Maya versions, see https://github.com/mottosso/docker-maya
with:
args: /usr/autodesk/maya/bin/mayapy -m unittest discover -s ./scripts/weights_editor_tool/tests -v

maya2022:
runs-on: ubuntu-latest

steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Load Docker Image
uses: docker://mottosso/maya:2022 # For all available Maya versions, see https://github.com/mottosso/docker-maya
with:
args: /usr/autodesk/maya/bin/mayapy -m unittest discover -s ./scripts/weights_editor_tool/tests -v

maya2022-1:
runs-on: ubuntu-latest

steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Load Docker Image
uses: docker://mottosso/maya:2022.1 # For all available Maya versions, see https://github.com/mottosso/docker-maya
with:
args: /usr/autodesk/maya/bin/mayapy -m unittest discover -s ./scripts/weights_editor_tool/tests -v
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.pyc
.idea/
__pycache__
8 changes: 7 additions & 1 deletion DRAG_AND_DROP_INSTALLER.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import os
import glob
import shutil
import stat
import traceback
import time
import maya.cmds as cmds
Expand Down Expand Up @@ -92,7 +93,12 @@ def onMayaDroppedPythonFile(*args):

if dialog == "Cancel":
return


# May need to tweak permissions before deleting.
for root, dirs, files in os.walk(tool_path, topdown=False):
for name in files + dirs:
os.chmod(os.path.join(root, name), stat.S_IWUSR)

shutil.rmtree(tool_path)

# Windows may throw an 'access denied' exception doing a copytree right after a rmtree.
Expand Down
Binary file added DRAG_AND_DROP_INSTALLER.pyc
Binary file not shown.
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ A skin weights component editor inspired from Softimage.

# Interface

![weightsEditorTable](https://user-images.githubusercontent.com/14979497/142756715-f782bc31-a079-4f20-8247-f29e91501b62.png)<br>
![weightsEditorTable](https://user-images.githubusercontent.com/14979497/148098205-d37b2533-c589-45fd-a84b-643963f1704c.png)<br>
_Interface using the table view_

<br>

![weightsEditorList](https://user-images.githubusercontent.com/14979497/142756748-acfb8f3b-f66a-4b5d-a80a-65211c89fb14.png)<br>
![weightsEditorList](https://user-images.githubusercontent.com/14979497/148098259-c2435bb9-cfff-42a7-8937-802cba182626.png)<br>
_Interface using the list view_

## Features
Expand All @@ -23,9 +23,20 @@ _Interface using the list view_
- Quickly lock or unlock selected influences by pressing space
- Influence list on the side
- Displays weights in different color themes
- Weight utilities to prune, smooth, mirror, and copy weights
- Weight utilities to prune, smooth, mirror, and copy/paste vertex weights
- Weights can be exported to a file
- Weights can be imported via point order, or by world space positions of the mesh's vertices. Weights can also import onto selected vertices, so you can maintain existing skin weights outside of the selection.
- Button to flood full weights to the vertex's closest influence for quick blocking
- All operations support undo/redo
- Temporary hotkeys that can be re-assigned

https://user-images.githubusercontent.com/14979497/148168582-5fb3e761-e70d-4904-be8e-a12da03faf3a.mp4

_Exporting weights then importing them onto a different object via world positions_

https://user-images.githubusercontent.com/14979497/148170835-fc301bd2-1dce-4f23-9632-02eb80eaa298.mp4

_Importing weights onto selected vertices_

## Supported versions

Expand Down
68 changes: 27 additions & 41 deletions scripts/weights_editor_tool/classes/command_edit_weights.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import maya.cmds as cmds
import copy

from maya import cmds
from PySide2 import QtWidgets

from weights_editor_tool import weights_editor_utils as utils
from weights_editor_tool.widgets import weights_table_view


class CommandEditWeights(QtWidgets.QUndoCommand):
Expand All @@ -24,56 +25,41 @@ def __init__(self, editor_cls, description, obj, old_skin_data, new_skin_data, v
table_selection, skip_first_redo=False, parent=None):
super(CommandEditWeights, self).__init__(description, parent=parent)

self.editor_cls = editor_cls
self.skip_first_redo = skip_first_redo
self.obj = obj
self.old_skin_data = old_skin_data
self.new_skin_data = new_skin_data
self.vert_indexes = vert_indexes
self.table_selection = table_selection
self._editor_cls = editor_cls
self._skip_first_redo = skip_first_redo
self._obj = obj
self._old_skin_data = old_skin_data
self._new_skin_data = new_skin_data
self._vert_indexes = vert_indexes
self._table_selection = table_selection

def redo(self):
if self.skip_first_redo:
self.skip_first_redo = False
return

if not self.obj or not cmds.objExists(self.obj):
def _edit_weights(self, skin_data):
if not self._obj or not cmds.objExists(self._obj):
return

weights_view = self.editor_cls.instance.get_active_weights_view()
weights_view = self._editor_cls.instance.get_active_weights_view()
old_column_count = weights_view.horizontalHeader().count()
weights_view.begin_update()

self.editor_cls.instance.skin_data = self.new_skin_data
utils.set_skin_weights(self.obj, self.new_skin_data, self.vert_indexes, normalize=True)
self.editor_cls.instance.update_vert_colors(vert_filter=self.vert_indexes)
self.editor_cls.instance.collect_display_infs()
self._editor_cls.instance.obj.skin_data = copy.deepcopy(skin_data)
self._editor_cls.instance.obj.apply_current_skin_weights(self._vert_indexes, normalize=True)
self._editor_cls.instance.update_vert_colors(vert_filter=self._vert_indexes)
self._editor_cls.instance.collect_display_infs()

weights_view.load_table_selection(self.table_selection)
weights_view.load_table_selection(self._table_selection)
weights_view.color_headers()

weights_view.end_update()

if weights_view.view_type == "table" and weights_view.horizontalHeader().count() != old_column_count:
if isinstance(weights_view, weights_table_view.TableView) and \
weights_view.horizontalHeader().count() != old_column_count:
weights_view.fit_headers_to_contents()

def undo(self):
if not self.obj or not cmds.objExists(self.obj):
return

weights_view = self.editor_cls.instance.get_active_weights_view()
old_column_count = weights_view.horizontalHeader().count()
weights_view.begin_update()

self.editor_cls.instance.skin_data = self.old_skin_data
utils.set_skin_weights(self.obj, self.old_skin_data, self.vert_indexes, normalize=True)
self.editor_cls.instance.update_vert_colors(vert_filter=self.vert_indexes)
self.editor_cls.instance.collect_display_infs()

weights_view.load_table_selection(self.table_selection)
weights_view.color_headers()

weights_view.end_update()
def redo(self):
if self._skip_first_redo:
self._skip_first_redo = False
else:
self._edit_weights(self._new_skin_data)

if weights_view.view_type == "table" and weights_view.horizontalHeader().count() != old_column_count:
weights_view.fit_headers_to_contents()
def undo(self):
self._edit_weights(self._old_skin_data)
24 changes: 12 additions & 12 deletions scripts/weights_editor_tool/classes/command_lock_infs.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import maya.cmds as cmds
from maya import cmds

from PySide2 import QtWidgets

Expand All @@ -17,37 +17,37 @@ class CommandLockInfs(QtWidgets.QUndoCommand):
def __init__(self, editor_cls, description, infs, enabled, parent=None):
super(CommandLockInfs, self).__init__(description, parent=parent)

self.editor_cls = editor_cls
self._editor_cls = editor_cls

# {inf_name, default_lock_state}
self.infs = {
self._infs = {
inf: cmds.getAttr("{0}.lockInfluenceWeights".format(inf))
for inf in infs}

self.enabled = enabled
self._enabled = enabled

def lock_infs(self, use_redo_value):
weights_view = self.editor_cls.instance.get_active_weights_view()
weights_view = self._editor_cls.instance.get_active_weights_view()

weights_view.begin_update()
self.editor_cls.instance.inf_list.begin_update()
self._editor_cls.instance.inf_list.begin_update()

for inf, enabled in self.infs.items():
if not cmds.objExists(inf) or inf not in self.editor_cls.instance.infs:
for inf, enabled in self._infs.items():
if not cmds.objExists(inf) or inf not in self._editor_cls.instance.obj.infs:
continue

if use_redo_value:
lock = self.enabled
lock = self._enabled
else:
lock = enabled

cmds.setAttr("{0}.lockInfluenceWeights".format(inf), lock)

inf_index = self.editor_cls.instance.infs.index(inf)
inf_index = self._editor_cls.instance.obj.infs.index(inf)

self.editor_cls.instance.locks[inf_index] = lock
self._editor_cls.instance.locks[inf_index] = lock

self.editor_cls.instance.inf_list.end_update()
self._editor_cls.instance.inf_list.end_update()
weights_view.end_update()

def redo(self):
Expand Down
42 changes: 23 additions & 19 deletions scripts/weights_editor_tool/classes/hotkey.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
from PySide2 import QtCore
from PySide2 import QtGui

from weights_editor_tool.enums import Hotkeys


class Hotkey:

Defaults = {
"Toggle table / list view": {"key": QtCore.Qt.Key_QuoteLeft, "ctrl": True},
"Show utilities": {"key": QtCore.Qt.Key_1, "ctrl": True},
"Show add presets": {"key": QtCore.Qt.Key_2, "ctrl": True},
"Show scale presets": {"key": QtCore.Qt.Key_3, "ctrl": True},
"Show set presets": {"key": QtCore.Qt.Key_4, "ctrl": True},
"Show inf list": {"key": QtCore.Qt.Key_5, "ctrl": True},
"Show inf colors": {"key": QtCore.Qt.Key_6, "ctrl": True},
"Mirror all": {"key": QtCore.Qt.Key_M, "ctrl": True},
"Prune": {"key": QtCore.Qt.Key_P, "ctrl": True},
"Run smooth (vert infs)": {"key": QtCore.Qt.Key_S, "ctrl": True, "shift": True},
"Run smooth (all infs)": {"key": QtCore.Qt.Key_D, "ctrl": True, "shift": True},
"Undo": {"key": QtCore.Qt.Key_Z, "ctrl": True, "shift": True},
"Redo": {"key": QtCore.Qt.Key_X, "ctrl": True, "shift": True},
"Grow selection": {"key": QtCore.Qt.Key_Greater},
"Shrink selection": {"key": QtCore.Qt.Key_Less},
"Select edge loop": {"key": QtCore.Qt.Key_E, "ctrl": True},
"Select ring loop": {"key": QtCore.Qt.Key_R, "ctrl": True},
"Select perimeter": {"key": QtCore.Qt.Key_T, "ctrl": True},
"Select shell": {"key": QtCore.Qt.Key_A, "ctrl": True, "shift": True}
Hotkeys.ToggleTableListViews: {"key": QtCore.Qt.Key_QuoteLeft, "ctrl": True},
Hotkeys.ShowUtilities: {"key": QtCore.Qt.Key_1, "ctrl": True},
Hotkeys.ShowAddPresets: {"key": QtCore.Qt.Key_2, "ctrl": True},
Hotkeys.ShowScalePresets: {"key": QtCore.Qt.Key_3, "ctrl": True},
Hotkeys.ShowSetPresets: {"key": QtCore.Qt.Key_4, "ctrl": True},
Hotkeys.ShowInfList: {"key": QtCore.Qt.Key_5, "ctrl": True},
Hotkeys.ShowInfColors: {"key": QtCore.Qt.Key_6, "ctrl": True},
Hotkeys.MirrorAll: {"key": QtCore.Qt.Key_M, "ctrl": True},
Hotkeys.Prune: {"key": QtCore.Qt.Key_P, "ctrl": True},
Hotkeys.RunSmooth: {"key": QtCore.Qt.Key_S, "ctrl": True, "shift": True},
Hotkeys.RunSmoothAllInfs: {"key": QtCore.Qt.Key_D, "ctrl": True, "shift": True},
Hotkeys.Undo: {"key": QtCore.Qt.Key_Z, "ctrl": True, "shift": True},
Hotkeys.Redo: {"key": QtCore.Qt.Key_X, "ctrl": True, "shift": True},
Hotkeys.GrowSelection: {"key": QtCore.Qt.Key_Greater},
Hotkeys.ShrinkSelection: {"key": QtCore.Qt.Key_Less},
Hotkeys.SelectEdgeLoop: {"key": QtCore.Qt.Key_E, "ctrl": True},
Hotkeys.SelectRingLoop: {"key": QtCore.Qt.Key_R, "ctrl": True},
Hotkeys.SelectPerimeter: {"key": QtCore.Qt.Key_T, "ctrl": True},
Hotkeys.SelectShell: {"key": QtCore.Qt.Key_A, "ctrl": True, "shift": True},
Hotkeys.ToggleInfLock: {"key": QtCore.Qt.Key_Space},
Hotkeys.ToggleInfLock2: {"key": QtCore.Qt.Key_L}
}

def __init__(self, caption, key, func, ctrl=False, shift=False, alt=False):
Expand Down
Loading

0 comments on commit 327de2d

Please sign in to comment.