Skip to content

Commit 64f76c2

Browse files
committed
Potential first release.
1 parent 67ea5c4 commit 64f76c2

File tree

8 files changed

+116
-118
lines changed

8 files changed

+116
-118
lines changed

README.md

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,28 @@
11
# febex
22

3-
This isn't ready for use yet, as it has no UI.
4-
But you can try your luck!
3+
This is a quick and dirty tool to scrub rigs of non-exportable data, allowing for FBXs to better
4+
represent rigs and animations as they are imported into Unity or Unreal.
5+
6+
## Installation:
7+
Unzip the entire febex folder into something that is in your maya path.
8+
If you'd rather put it somewhere else, you can path it like so:
9+
```
10+
import sys
11+
# The path where the febex folder is (change \ to /!)
12+
sys.path.append("install folder of febex")
13+
```
14+
Just make sure the folder where the code lives is called "febex" (If not, account for that in
15+
upcoming steps).
16+
17+
## Running in Maya
18+
Put the following in a script editor or shelf button-- this will work if the febex folder is pathed
19+
correctly (see above).
20+
```
21+
import febex.ui as fb
22+
ui = fb.Febex_Ui()
23+
```
24+
25+
## A word on 'state'
26+
This UI won't recall the lists of influences it just made if you close and re-open it, so try to
27+
perform all your operations with the window remaining open. You may have to reload your file after
28+
you've run a procedure on it.

mesh.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def __init__(self, mesh: str):
3333
if cmds.objectType(mesh) == "transform":
3434
self.trans_node = mesh
3535
relatives = cmds.listRelatives(mesh, s=True)
36-
if(relatives is None):
36+
if relatives is None:
3737
raise TypeError(f"{mesh} doesn't have a shape node.")
3838
else:
3939
self.mesh_node = relatives[0]
@@ -43,7 +43,6 @@ def __init__(self, mesh: str):
4343
else:
4444
raise TypeError(f"{mesh} is of type {cmds.objectType(mesh)}, not 'mesh'")
4545

46-
4746
@property
4847
def shape(self):
4948
return self.mesh_node

operations.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,19 @@
1212
from .mesh import MeshData
1313

1414

15-
def build_export_content(target_geo: MeshData, top_joint: str, new_mesh=True):
15+
def build_export_content(target_geo: MeshData, top_joint: str, new_mesh=True) -> tuple:
16+
"""This will create a group in the scene that will have save components to export as FBX to a
17+
game engine.
18+
19+
Args:
20+
target_geo (MeshData): The skin-cluster mesh (should be just one!)
21+
top_joint (str): Top of the joint hierarchy.
22+
new_mesh (bool, optional): Whether a mesh should be duplicated or just a skeleton.
23+
Defaults to True.
24+
25+
Returns:
26+
tuple: The new and old influences to be used in later operations.
27+
"""
1628
mesh = MeshData(target_geo)
1729
print(f"Mesh shape node is {mesh.mesh_node}.")
1830

@@ -35,18 +47,26 @@ def build_export_content(target_geo: MeshData, top_joint: str, new_mesh=True):
3547
ia=["closestBone", "closestJoint", "name"],
3648
)
3749

50+
return (old_influences, new_influences)
3851

3952

40-
def bake_animated_skeleton(old_influences, new_influences):
53+
def bake_animated_skeleton(old_influences: list, new_influences: list):
54+
"""Runs a bake simulation on the influences of the exported skeleton.
55+
56+
Args:
57+
old_influences (list): Influence list of the original rig.
58+
new_influences (list): Influence list of the export skeleton.
59+
"""
4160
skeleton.bind_exported_skeleton(old_influences)
4261

43-
current_frame = int(cmds.currentTime(query=True))
62+
start_time = cmds.playbackOptions(query=True, minTime=True)
63+
end_time = cmds.playbackOptions(query=True, maxTime=True)
4464

4565
cmds.select(new_influences, r=True)
4666

4767
cmds.bakeResults(
4868
simulation=True,
49-
t=(current_frame, current_frame),
69+
t=(start_time, end_time),
5070
sampleBy=1,
5171
disableImplicitControl=True,
5272
preserveOutsideKeys=True,
@@ -57,4 +77,3 @@ def bake_animated_skeleton(old_influences, new_influences):
5777
controlPoints=False,
5878
shape=True,
5979
)
60-

progbar.py

Lines changed: 0 additions & 67 deletions
This file was deleted.

skeleton.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,13 @@ def copy_influence_tree(top_joint: str, inf_list: str) -> list:
9494

9595

9696
def bind_exported_skeleton(old_infs: list):
97-
97+
"""Binds 1:1 the original influences to the new influences with parent constraints set to
98+
'no-flip'.
99+
100+
Args:
101+
old_infs (list): The list of old influences.
102+
"""
103+
98104
print("Binding new export skeleton to old skeleton...")
99105
for jnt in old_infs:
100106
pconstraint = cmds.parentConstraint(jnt, f"{jnt}_INF", mo=False)[0]

skinning.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def find_cluster_node(node: str) -> str:
3939
node, source=True, destination=False, type="skinCluster"
4040
)
4141

42-
print(f"Clusters found are: {clusters}" )
42+
print(f"Clusters found are: {clusters}")
4343
# Guard against no clusters or not enough clusters being found.
4444
if clusters is None:
4545
# The cluster might not exist, or there are nodes between it and out mesh. Falling back to
@@ -53,10 +53,10 @@ def find_cluster_node(node: str) -> str:
5353
# Loop through the history nodes and find the skin cluster
5454
for node in history_nodes:
5555
node_type = cmds.nodeType(node)
56-
if node_type == 'skinCluster':
56+
if node_type == "skinCluster":
5757
skin_cluster_node = node
5858
break
59-
if(skin_cluster_node is None):
59+
if skin_cluster_node is None:
6060
raise AttributeError(f"There are no skinclusters attached to {node}")
6161
else:
6262
clusters = [skin_cluster_node]
@@ -126,5 +126,3 @@ def copy_skinning(old_mesh: str, new_mesh: str):
126126
new_cluster, dest_vert, transformValue=(dest_joint, weight)
127127
)
128128
current_vert += 1
129-
130-
# cmds.copySkinWeights(ss=old_cluster, ds=new_cluster, nm=True, sa="closestPoint", ia="name")

ui.py

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,38 @@ def _init_ui(self):
108108
lambda: self._build_export_rig()
109109
)
110110

111+
self.BakeAnim_QPushButton_QPushButton.clicked.connect(
112+
lambda: self._bake_skeleton()
113+
)
114+
111115
self.show()
116+
self._validate_bake_button()
117+
self._validate_export_rig_button()
112118

113119
def _build_export_rig(self):
114-
ops.build_export_content(self.state.selected_mesh, self.state.selected_joint)
120+
"""Wraps the operations.build_export_content() function.
121+
"""
122+
infs = ops.build_export_content(
123+
self.state.selected_mesh, self.state.selected_joint
124+
)
125+
self.state.old_influence_list = infs[0]
126+
self.state.new_influence_list = infs[1]
115127
self._validate_bake_button()
116128

117-
def _get_selection(self, type):
129+
def _bake_skeleton(self):
130+
"""Wraps the operations.bake_animated_skeleton() function.
131+
"""
132+
print(f"Reviewing state:\n{self.state.old_influence_list}\n{self.state.new_influence_list}")
133+
ops.bake_animated_skeleton(
134+
self.state.old_influence_list, self.state.new_influence_list
135+
)
136+
137+
def _get_selection(self, type: int):
138+
"""Populates a lineedit field with something selected from the scene.
139+
140+
Args:
141+
type (int): 0 for mesh selection, 1 for joint selection.
142+
"""
118143
selection = cmds.ls(sl=True)
119144

120145
if len(selection) != 1:
@@ -158,14 +183,18 @@ def _get_selection(self, type):
158183
self._validate_export_rig_button()
159184

160185
def _validate_bake_button(self):
161-
186+
"""Based on context, determine if the bake button should be usable.
187+
"""
162188
valid = True
163189
if cmds.objExists("export_group") == False:
164190
valid = False
165191

166192
self.BakeAnim_QPushButton_QPushButton.setEnabled(valid)
167193

168194
def _validate_export_rig_button(self):
195+
"""Based on state and scene context, determines if the Build export rig button should be
196+
available.
197+
"""
169198
valid = True
170199

171200
mesh_object = self.SelectedMesh_QLineEdit_QLineEdit.text()
@@ -220,6 +249,9 @@ def __init__(self):
220249
self._last_rig_path = None
221250
self._last_anim_path = None
222251

252+
self._old_inf_list = None
253+
self._new_inf_list = None
254+
223255
@property
224256
def selected_mesh(self):
225257
return self._selected_mesh
@@ -251,4 +283,20 @@ def anim_path(self):
251283

252284
@anim_path.setter
253285
def anim_path(self, value):
254-
self._last_anim_path
286+
self._last_anim_path = value
287+
288+
@property
289+
def new_influence_list(self):
290+
return self._new_inf_list
291+
292+
@new_influence_list.setter
293+
def new_influence_list(self, value):
294+
self._new_inf_list = value
295+
296+
@property
297+
def old_influence_list(self):
298+
return self._old_inf_list
299+
300+
@old_influence_list.setter
301+
def old_influence_list(self, value):
302+
self._old_inf_list = value

uis/febex_main.ui

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<x>0</x>
88
<y>0</y>
99
<width>656</width>
10-
<height>289</height>
10+
<height>187</height>
1111
</rect>
1212
</property>
1313
<property name="windowTitle">
@@ -19,14 +19,14 @@
1919
<x>9</x>
2020
<y>9</y>
2121
<width>641</width>
22-
<height>266</height>
22+
<height>171</height>
2323
</rect>
2424
</property>
2525
<layout class="QVBoxLayout" name="verticalLayout">
2626
<item>
2727
<widget class="QLabel" name="Version_QLabel">
2828
<property name="text">
29-
<string>Febex 1.0.0</string>
29+
<string>Febex - The simple FBX export prepper.</string>
3030
</property>
3131
</widget>
3232
</item>
@@ -129,37 +129,8 @@
129129
</property>
130130
</widget>
131131
</item>
132-
<item>
133-
<widget class="QPushButton" name="pushButton_2">
134-
<property name="enabled">
135-
<bool>false</bool>
136-
</property>
137-
<property name="toolTip">
138-
<string extracomment="Exports and .FBX of the Deformable Mesh derived from the rig."/>
139-
</property>
140-
<property name="text">
141-
<string>Export Deformable Mesh</string>
142-
</property>
143-
</widget>
144-
</item>
145-
<item>
146-
<widget class="QPushButton" name="pushButton_3">
147-
<property name="enabled">
148-
<bool>false</bool>
149-
</property>
150-
<property name="toolTip">
151-
<string extracomment="Exports and FBX of just the animated skeleton."/>
152-
</property>
153-
<property name="text">
154-
<string>Export Animated Joints</string>
155-
</property>
156-
</widget>
157-
</item>
158132
</layout>
159133
</item>
160-
<item>
161-
<widget class="QTextBrowser" name="Output"/>
162-
</item>
163134
</layout>
164135
</widget>
165136
</widget>

0 commit comments

Comments
 (0)