Files
MetaBox/Scripts/Modeling/Edit/XGenUEGroomExporter.py
2025-04-17 04:52:48 +08:00

851 lines
32 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# %%
import alembic.Abc as abc
import alembic.AbcGeom as abcGeom
import alembic.AbcCoreAbstract as abcA
import maya.OpenMaya as om1
import maya.api.OpenMayaAnim as omAnim
import maya.api.OpenMaya as om
import imath
import array
import zlib
import json
import maya.cmds as cmds
from typing import List
import time
import struct
# %%
print_debug = False
# %%
def list2ImathArray(l: list, _type):
arr = _type(len(l))
for i in range(len(l)):
arr[i] = l[i]
return arr
def floatList2V3fArray(l: list):
arr = imath.V3fArray(len(l) // 3)
for i in range(len(arr)):
arr[i].x = l[i * 3]
arr[i].y = l[i * 3 + 1]
arr[i].z = l[i * 3 + 2]
return arr
# %%
def getXgenData(fnDepNode: om.MFnDependencyNode):
splineData: om.MPlug = fnDepNode.findPlug("outSplineData", False)
handle: om.MDataHandle = splineData.asMObject()
mdata = om.MFnPluginData(handle)
mData = mdata.data()
rawData = mData.writeBinary()
def GetBlocks(bype_data):
address = 0
i = 0
blocks = []
maxIt = 100
while address < len(bype_data) - 1:
size = int.from_bytes(bype_data[address + 8:address + 16], byteorder='little', signed=False)
type_code = int.from_bytes(bype_data[address:address + 4], byteorder='little', signed=False)
blocks.append((address + 16, address + 16 + size, type_code))
address += size + 16
i += 1
if i > maxIt:
break
return blocks
dataBlocks = GetBlocks(rawData)
headerBlock = dataBlocks[0]
dataBlocks.pop(0)
dataJson = json.loads(rawData[headerBlock[0]:headerBlock[1]])
# print(dataJson)
Header = dataJson['Header']
Items = dict()
def readItems(items):
for k, v in items:
if isinstance(v, int):
group = v >> 32
index = v & 0xFFFFFFFF
addr = (group, index)
if k not in Items:
Items[k] = [addr]
else:
Items[k].append(addr)
for i in range(len(dataJson['Items'])):
readItems(dataJson['Items'][i].items())
for i in range(len(dataJson['RefMeshArray'])):
readItems(dataJson['RefMeshArray'][i].items())
# print(Items)
decompressedData = dict()
def decompressData(group, index):
if group not in decompressedData:
if Header['GroupBase64']:
raise Exception("我还没有碰到Base64的情况请提醒我更新代码")
if Header['GroupDeflate']:
validData = zlib.decompress(rawData[dataBlocks[group][0] + 32:])
else:
validData = rawData[dataBlocks[group][0]:dataBlocks[group][1]]
decompressedData[group] = validData
else:
validData = decompressedData[group]
blocks = GetBlocks(validData)
return validData[blocks[index][0]:blocks[index][1]]
PrimitiveInfosList = []
PositionsDataList = []
WidthsDataList = []
for k, v in Items.items():
# print(k, len(v))
if k == 'PrimitiveInfos':
dtype_format = '<IQ'
for addr in v:
decompressed_data = decompressData(*addr) # 假设解压缩返回字节流
PrimitiveInfos = []
record_size = struct.calcsize(dtype_format)
for i in range(0, len(decompressed_data), record_size):
PrimitiveInfo = struct.unpack_from(f'{dtype_format}', decompressed_data, i)
PrimitiveInfos.append(PrimitiveInfo)
PrimitiveInfosList.append(PrimitiveInfos)
if k == 'Positions':
for addr in v:
decompressed_data = decompressData(*addr)
posData = array.array('f', decompressed_data)
PositionsDataList.append(posData)
if k == 'WIDTH_CV':
for addr in v:
decompressed_data = decompressData(*addr)
widthData = array.array('f', decompressed_data)
WidthsDataList.append(widthData)
return PrimitiveInfosList, PositionsDataList, WidthsDataList
# %%
class CurvesProxy:
def __init__(self, curveObj: abcGeom.OCurves, fnDepNode: om.MFnDependencyNode, needBakeUV=False, animation=False):
self.hairRootList = None
self.schema: abcGeom.OCurvesSchema = curveObj.getSchema()
self.needBakeUV = needBakeUV
self.animation = animation
self.firstSamp = abcGeom.OCurvesSchemaSample()
self.fnDepNode = fnDepNode
self.curves = None
self.groupName = None
def write_group_name(self, group_name: str):
cp: abc.OCompoundProperty = self.schema.getArbGeomParams()
groupName = abc.OStringArrayProperty(cp, "groom_group_name")
groupName.setValue(list2ImathArray([group_name], imath.StringArray))
self.groupName = group_name
def write_is_guide(self, is_guide=True):
cp: abc.OCompoundProperty = self.schema.getArbGeomParams()
if is_guide:
guideFlag = abc.OInt16ArrayProperty(cp, "groom_guide")
guideFlag.setValue(list2ImathArray([1], imath.ShortArray))
def write_group_id(self, group_id: int):
cp: abc.OCompoundProperty = self.schema.getArbGeomParams()
_id = abc.OInt32ArrayProperty(cp, "groom_group_id")
_id.setValue(list2ImathArray([group_id], imath.IntArray))
def write_first_frame(self):
itDag = om.MItDag()
itDag.reset(self.fnDepNode.object(), om.MItDag.kDepthFirst, om.MFn.kCurve)
curves = []
while not itDag.isDone():
curve_node = itDag.currentItem()
curves.append(curve_node)
itDag.next()
self.curves = curves
numCurves = len(self.curves)
if numCurves == 0:
return
curve = om.MFnNurbsCurve(self.curves[0])
orders = imath.IntArray(numCurves)
nVertices = imath.IntArray(numCurves)
pointslist = []
knots = []
if self.needBakeUV:
self.hairRootList = []
samp = self.firstSamp
samp.setBasis(abcGeom.BasisType.kBsplineBasis)
samp.setWrap(abcGeom.CurvePeriodicity.kNonPeriodic)
if curve.degree == 3:
samp.setType(abcGeom.CurveType.kCubic)
elif curve.degree == 1:
samp.setType(abcGeom.CurveType.kLinear)
else:
# samp.setType(abcGeom.CurveType.kVariableOrder)
samp.setType(abcGeom.CurveType.kLinear)
pass
for i in range(numCurves):
curve = curve.setObject(self.curves[i])
numCVs = curve.numCVs
orders[i] = curve.degree + 1
nVertices[i] = numCVs
cvArray = curve.cvPositions()
for j in range(numCVs):
pointslist.append(cvArray[j].x)
pointslist.append(cvArray[j].y)
pointslist.append(cvArray[j].z)
if self.needBakeUV:
self.hairRootList.append(cvArray[0])
knotsArray = curve.knots()
if len(knotsArray) > 1:
knotsLength = len(knotsArray)
if (knotsArray[0] == knotsArray[knotsLength - 1] or
knotsArray[0] == knotsArray[1]):
knots.append(float(knotsArray[0]))
else:
knots.append(float(2 * knotsArray[0] - knotsArray[1]))
for j in range(knotsLength):
knots.append(float(knotsArray[j]))
if (knotsArray[0] == knotsArray[knotsLength - 1] or
knotsArray[knotsLength - 1] == knotsArray[knotsLength - 2]):
knots.append(float(knotsArray[knotsLength - 1]))
else:
knots.append(float(2 * knotsArray[knotsLength - 1] - knotsArray[knotsLength - 2]))
samp.setCurvesNumVertices(nVertices)
samp.setPositions(floatList2V3fArray(pointslist))
samp.setOrders(list2ImathArray(orders, imath.UnsignedCharArray))
samp.setKnots(list2ImathArray(knots, imath.FloatArray))
# widths = list2ImathArray([0.1], imath.FloatArray)
# widths = abc.Float32TPTraits()
# widths = abcGeom.OFloatGeomParamSample(widths, abcGeom.GeometryScope.kConstantScope)
# samp.setWidths(widths)
self.schema.set(samp)
def write_frame(self):
numCurves = len(self.curves)
if numCurves == 0:
return
curve = om.MFnNurbsCurve(self.curves[0])
samp = abcGeom.OCurvesSchemaSample()
samp.setBasis(self.firstSamp.getBasis())
samp.setWrap(self.firstSamp.getWrap())
samp.setType(self.firstSamp.getType())
samp.setCurvesNumVertices(self.firstSamp.getCurvesNumVertices())
samp.setOrders(self.firstSamp.getOrders())
samp.setKnots(self.firstSamp.getKnots())
pointslist = []
for i in range(numCurves):
curve = curve.setObject(self.curves[i])
numCVs = curve.numCVs
cvArray = curve.cvPositions()
for j in range(numCVs):
pointslist.append(cvArray[j].x)
pointslist.append(cvArray[j].y)
pointslist.append(cvArray[j].z)
samp.setPositions(floatList2V3fArray(pointslist))
self.schema.set(samp)
def back_uv(self, bakeMesh: om.MFnMesh, uv_set: str = None):
if self.hairRootList is None:
return
if bakeMesh is None:
return
if uv_set is None:
uv_set = bakeMesh.currentUVSetName()
elif uv_set not in bakeMesh.getUVSetNames():
raise Exception(f'Invalid UV Set : {uv_set}')
uvs = imath.V2fArray(len(self.hairRootList))
for i, hairRoot in enumerate(self.hairRootList):
res = bakeMesh.getUVAtPoint(hairRoot, om.MSpace.kWorld, uvSet=uv_set)
uvs[i].x = res[0]
uvs[i].y = res[1]
cp: abc.OCompoundProperty = self.schema.getArbGeomParams()
uv_prop = abc.OV2fArrayProperty(cp, "groom_root_uv")
uv_prop.setValue(uvs)
class XGenProxy(CurvesProxy):
def __init__(self, curveObj: abcGeom.OCurves, fnDepNode: om.MFnDependencyNode, needBakeUV=False, animation=False):
super().__init__(curveObj, fnDepNode, needBakeUV, animation)
def write_first_frame(self):
if print_debug:
startTime = time.time()
PrimitiveInfosList, PositionsDataList, WidthsDataList = getXgenData(self.fnDepNode)
if print_debug:
print("getXgenData: %.4f" % (time.time() - startTime))
startTime = time.time()
numCurves = 0
numCVs = 0
for i, PrimitiveInfos in enumerate(PrimitiveInfosList):
numCurves += len(PrimitiveInfos)
for PrimitiveInfo in PrimitiveInfos:
numCVs += PrimitiveInfo[1]
orders = imath.UnsignedCharArray(numCurves)
nVertices = imath.IntArray(numCurves)
cp: abc.OCompoundProperty = self.schema.getArbGeomParams()
samp = self.firstSamp
samp.setBasis(abcGeom.BasisType.kBsplineBasis)
samp.setWrap(abcGeom.CurvePeriodicity.kNonPeriodic)
samp.setType(abcGeom.CurveType.kCubic)
degree = 3
pointArray = imath.V3fArray(numCVs)
widthArray = imath.FloatArray(numCVs)
if self.needBakeUV:
self.hairRootList = []
knots = []
curveIndex = 0
cvIndex = 0
for j in range(len(PrimitiveInfosList)):
PrimitiveInfos = PrimitiveInfosList[j]
posData = PositionsDataList[j]
widthData = WidthsDataList[j]
for i, PrimitiveInfo in enumerate(PrimitiveInfos):
offset = PrimitiveInfo[0]
length = int(PrimitiveInfo[1])
if length < 2:
continue
startAddr = offset * 3
for k in range(length):
pointArray[cvIndex].x = posData[startAddr]
pointArray[cvIndex].y = posData[startAddr + 1]
pointArray[cvIndex].z = posData[startAddr + 2]
if k == 0 and self.needBakeUV:
self.hairRootList.append(om.MPoint(pointArray[cvIndex]))
widthArray[cvIndex] = widthData[offset + k]
startAddr += 3
cvIndex += 1
orders[curveIndex] = degree + 1
nVertices[curveIndex] = length
knotsInsideNum = length - degree + 1
knotsList = [*([0] * degree), *list(range(knotsInsideNum)),
*([knotsInsideNum - 1] * degree )]
knots += knotsList
curveIndex += 1
samp.setCurvesNumVertices(nVertices)
samp.setPositions(pointArray)
samp.setKnots(list2ImathArray(knots, imath.FloatArray))
samp.setOrders(orders)
# back vertex color example
# cvColor = abcGeom.OC3fGeomParam(cp, "groom_color", False, abcGeom.GeometryScope.kVertexScope, 1)
# cvColorArray = imath.C3fArray(len(pointslist) // 3)
# i = 0
# color1 = imath.Color3f((0, 1, 1));
# color2 = imath.Color3f((1, 0, 1))
# for _ in range(len(nVertices)):
# length = nVertices[_]
# for j in range(length):
# t = (j / (length - 1))
# cvColorArray[i] = color1 * (1 - t) + color2 * t
# i += 1
# cvColorArray = abcGeom.OC3fGeomParamSample(cvColorArray, abcGeom.GeometryScope.kVertexScope)
# cvColor.set(cvColorArray)
# write width
widths = abcGeom.OFloatGeomParamSample(widthArray, abcGeom.GeometryScope.kVertexScope)
samp.setWidths(widths)
self.schema.set(samp)
if print_debug:
print("write_first_frame: %.4f" % (time.time() - startTime))
def write_frame(self):
if print_debug:
startTime = time.time()
PrimitiveInfosList, PositionsDataList, WidthsDataList = getXgenData(self.fnDepNode)
numCurves = 0
numCVs = 0
for i, PrimitiveInfos in enumerate(PrimitiveInfosList):
numCurves += len(PrimitiveInfos)
for PrimitiveInfo in PrimitiveInfos:
numCVs += PrimitiveInfo[1]
cp: abc.OCompoundProperty = self.schema.getArbGeomParams()
samp = abcGeom.OCurvesSchemaSample()
samp.setBasis(self.firstSamp.getBasis())
samp.setWrap(self.firstSamp.getWrap())
samp.setType(self.firstSamp.getType())
samp.setCurvesNumVertices(self.firstSamp.getCurvesNumVertices())
samp.setKnots(self.firstSamp.getKnots())
samp.setOrders(self.firstSamp.getOrders())
samp.setWidths(self.firstSamp.getWidths())
pointArray = imath.V3fArray(numCVs)
curveIndex = 0
cvIndex = 0
for j in range(len(PrimitiveInfosList)):
PrimitiveInfos = PrimitiveInfosList[j]
posData = PositionsDataList[j]
for i, PrimitiveInfo in enumerate(PrimitiveInfos):
offset = PrimitiveInfo[0]
length = int(PrimitiveInfo[1])
if length < 2:
continue
startAddr = offset * 3
for k in range(length):
pointArray[cvIndex].x = posData[startAddr]
pointArray[cvIndex].y = posData[startAddr + 1]
pointArray[cvIndex].z = posData[startAddr + 2]
startAddr += 3
cvIndex += 1
curveIndex += 1
samp.setPositions(pointArray)
self.schema.set(samp)
if print_debug:
print("write_frame: %.4f" % (time.time() - startTime))
# %%
try:
from PySide6 import QtCore, QtWidgets, QtGui
import shiboken6 as shiboken
except:
from PySide2 import QtCore, QtWidgets, QtGui
import shiboken2 as shiboken
import maya.OpenMayaUI as om1ui
def mayaWindow():
main_window_ptr = om1ui.MQtUtil.mainWindow()
return shiboken.wrapInstance(int(main_window_ptr), QtWidgets.QWidget)
# %%
class SaveXGenWindow(QtWidgets.QDialog):
class Content:
def __init__(self, fnDepNode, showName, Type, groupName, isGuide, bakeUV, animation, export):
self.showName = showName
self.fnDepNode = fnDepNode
self.Type = Type
self.groupName = QtWidgets.QLineEdit()
self.groupName.setText(groupName)
self.isGuide = QtWidgets.QCheckBox()
self.isGuide.setChecked(isGuide)
self.bakeUV = QtWidgets.QCheckBox()
self.bakeUV.setChecked(bakeUV)
self.animation = QtWidgets.QCheckBox()
self.animation.setChecked(animation)
self.export = QtWidgets.QCheckBox()
self.export.setChecked(export)
curveType = "curve"
xgenType = "xgen"
def __init__(self, parent=mayaWindow()):
super(SaveXGenWindow, self).__init__(parent)
self.contentList: List[SaveXGenWindow.Content] = []
self.save_path = '.'
self.bakeMesh = None
self.setWindowTitle("Export XGen to UE Groom")
self.setGeometry(400, 400, 850, 450)
self.buildUI()
def showAbout(self):
QtWidgets.QMessageBox.about(self, "Export XGen to UE Groom",
"A small tool to export XGen to UE Groom, by PDE26jjk. Link: <a href='https://github.com/PDE26jjk/XGenUEGroomExporter'>https://github.com/PDE26jjk/XGenUEGroomExporter</a>")
def createFrame(self, labelText):
try:
frame = om1ui.MQtUtil.findControl(
cmds.frameLayout(label=labelText, collapsable=True, collapse=True, manage=True))
frame: QtWidgets.QWidget = shiboken.wrapInstance(int(frame), QtWidgets.QWidget)
frame.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Maximum)
frameLayout: QtWidgets.QLayout = frame.children()[2].children()[0]
except:
frame = QtWidgets.QFrame(self)
frame.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Maximum)
frameLayout = QtWidgets.QVBoxLayout(frame)
frame.children().append(frameLayout)
return frame, frameLayout
def buildUI(self):
main_layout = QtWidgets.QVBoxLayout()
menu_bar = QtWidgets.QMenuBar(self)
menu_bar.addMenu("Help").addAction("About", self.showAbout)
main_layout.setMenuBar(menu_bar)
label1 = QtWidgets.QLabel("Please select Curves and Interactive XGen") # 请选择曲线和交互式XGen
label1.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
hBox = QtWidgets.QHBoxLayout()
hBox.setContentsMargins(10, 4, 10, 4)
hBox.addWidget(label1)
self.fillWithSelectList_button = QtWidgets.QPushButton("Refresh selected") # 刷新选择
self.fillWithSelectList_button.clicked.connect(self.fillWithSelectList)
self.fillWithSelectList_button.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
# main_layout.addWidget(self.fillWithSelectList_button)
hBox.addStretch(1)
hBox.addWidget(self.fillWithSelectList_button)
main_layout.addLayout(hBox)
self.table = QtWidgets.QTableWidget(self)
self.table.setColumnCount(8) # 设置列数
self.table.setHorizontalHeaderLabels(["", "Name", "Type", "Group name", "Is guide", "Bake UV", "Animation", ""])
self.table.horizontalHeader().setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
self.table.setColumnWidth(0, 40)
self.table.horizontalHeader().setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents)
self.table.setColumnWidth(4, 140)
self.table.horizontalHeader().setSectionResizeMode(4, QtWidgets.QHeaderView.Fixed)
self.table.setColumnWidth(5, 140)
self.table.horizontalHeader().setSectionResizeMode(5, QtWidgets.QHeaderView.Fixed)
self.table.setColumnWidth(6, 140)
self.table.horizontalHeader().setSectionResizeMode(6, QtWidgets.QHeaderView.Fixed)
self.table.horizontalHeader().setStretchLastSection(True)
self.table.setStyleSheet("""
QTableView::item
{
border: 0px;
padding: 5px;
background-color: rgb(68, 68, 68);
}
QTableView::item QCheckBox {
padding-left:60px;
}
""")
self.table.clearContents()
self.table.setRowCount(0)
self.Bakeframe, frameLayout = self.createFrame(labelText="Bake UV")
self.MeshName = QtWidgets.QLabel("Mesh : ---")
hBox = QtWidgets.QHBoxLayout()
hBox.setContentsMargins(10, 10, 10, 10)
hBox.addWidget(self.MeshName)
hBox2 = QtWidgets.QHBoxLayout()
label = QtWidgets.QLabel("UV Set : ")
hBox2.addWidget(label)
self.combo = QtWidgets.QComboBox()
self.combo.addItem(" --- ")
self.uvSetStr = QtWidgets.QLabel("Selected: None")
self.combo.currentIndexChanged.connect(self.update_label)
hBox2.addWidget(self.combo)
hBox.addStretch(2)
hBox.addLayout(hBox2)
hBox.addStretch(1)
frameLayout.addLayout(hBox)
self.button3 = QtWidgets.QPushButton("Pick other mesh", self)
self.button3.clicked.connect(self.pick_mesh)
self.button3.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
frameLayout.addWidget(self.button3)
self.separator = QtWidgets.QFrame(self)
self.separator.setFrameShape(QtWidgets.QFrame.HLine)
self.separator.setFrameShadow(QtWidgets.QFrame.Sunken)
self.AnimationFrame, frameLayout = self.createFrame(labelText="Animation")
validator = QtGui.QIntValidator()
validator.setRange(0, 99999)
self.startFrame = QtWidgets.QLineEdit()
self.startFrame.setMaximumWidth(60)
self.startFrame.setValidator(validator)
self.startFrame.setText(str(0))
self.endFrame = QtWidgets.QLineEdit()
self.endFrame.setMaximumWidth(60)
self.endFrame.setValidator(validator)
self.endFrame.setText(str(0))
self.preroll = QtWidgets.QCheckBox("Preroll")
frameLayout.setContentsMargins(10, 10, 10, 10)
hBox = QtWidgets.QHBoxLayout()
hBox.addWidget(QtWidgets.QLabel("Frame Range : "))
hBox.addWidget(self.startFrame)
hBox.addWidget(QtWidgets.QLabel(" ~ "))
hBox.addWidget(self.endFrame)
hBox.addStretch(1)
hBox2 = QtWidgets.QHBoxLayout()
hBox2.addWidget(self.preroll)
hBox2.addStretch(1)
frameLayout.addLayout(hBox)
frameLayout.addLayout(hBox2)
self.SettingFrame, frameLayout = self.createFrame(labelText="Setting")
frameLayout.setContentsMargins(10, 10, 10, 10)
hBox = QtWidgets.QHBoxLayout()
self.createGroupId_cb = QtWidgets.QCheckBox("Create group id")
self.createGroupId_cb.setChecked(True)
hBox.addWidget(self.createGroupId_cb)
frameLayout.addLayout(hBox)
self.save_button = QtWidgets.QPushButton("Save Alembic File", self)
self.save_button.clicked.connect(self.save_abc)
self.cancel_button = QtWidgets.QPushButton("Close", self) # 关闭
self.cancel_button.clicked.connect(self.close) # 关闭窗口
button_layout = QtWidgets.QHBoxLayout()
button_layout.addWidget(self.save_button)
button_layout.addWidget(self.cancel_button)
main_layout.addWidget(self.table)
main_layout.addWidget(self.Bakeframe)
main_layout.addWidget(self.AnimationFrame)
main_layout.addWidget(self.SettingFrame)
main_layout.addWidget(self.separator)
main_layout.addLayout(button_layout)
self.setLayout(main_layout)
def pick_mesh(self):
selectionList = om.MGlobal.getActiveSelectionList()
if selectionList.length() > 0:
dag_path = selectionList.getDagPath(0)
fnDepNode = om.MFnDependencyNode(dag_path.node())
itDag = om.MItDag()
# find mesh
itDag.reset(fnDepNode.object(), om.MItDag.kDepthFirst, om.MFn.kMesh)
while not itDag.isDone():
meshPath = om.MDagPath.getAPathTo(itDag.currentItem())
mesh = om.MFnMesh(meshPath)
self.setBakeMesh(mesh)
break
def update_label(self):
selected_option = self.combo.currentText()
self.uvSetStr.setText(selected_option)
def save_abc(self):
if len(self.contentList) == 0:
print("No content")
return
file_path = cmds.fileDialog2(
# dialogStyle=2,
caption="Save as Alembic File",
fileMode=0,
okCaption="save",
# defaultExtension='abc',
startingDirectory=self.save_path,
ff='Alembic Files (*.abc);;All Files (*)'
)
if file_path:
self.save_path = file_path[0]
else:
return
startTime = time.time()
oldCurTime = omAnim.MAnimControl.currentTime()
archive = abc.OArchive(file_path[0])
anyAnimation = False
for item in self.contentList:
hasAnimation = item.animation.isChecked()
if hasAnimation:
anyAnimation = True
break
if anyAnimation:
frameRange = [int(self.startFrame.text()), int(self.endFrame.text())]
if (frameRange[0] > frameRange[1]
or frameRange[0] < omAnim.MAnimControl.minTime().value
or frameRange[1] > omAnim.MAnimControl.maxTime().value):
raise ValueError("Frame out of range.")
# frameRange[0] = int(max(frameRange[0], omAnim.MAnimControl.minTime().value))
# frameRange[1] = int(min(frameRange[1], omAnim.MAnimControl.maxTime().value))
sec = om.MTime(1, om.MTime.kSeconds)
spf = 1.0 / sec.asUnits(om.MTime.uiUnit())
timeSampling = abcA.TimeSampling(spf, spf * frameRange[0])
timeIndex = archive.addTimeSampling(timeSampling)
proxyList: List[CurvesProxy] = []
for item in self.contentList:
if not item.export:
continue
fnDepNode = item.fnDepNode
needBakeUV = item.bakeUV.isChecked()
hasAnimation = item.animation.isChecked()
if hasAnimation:
curveObj = abcGeom.OCurves(archive.getTop(), fnDepNode.name(), timeIndex)
else:
curveObj = abcGeom.OCurves(archive.getTop(), fnDepNode.name())
if item.Type == SaveXGenWindow.xgenType:
proxy = XGenProxy(curveObj, fnDepNode, needBakeUV, hasAnimation)
elif item.Type == SaveXGenWindow.curveType:
proxy = CurvesProxy(curveObj, fnDepNode, needBakeUV, hasAnimation)
else:
continue
proxyList.append(proxy)
proxy.write_group_name(item.groupName.text())
proxy.write_is_guide(item.isGuide.isChecked())
if len(proxyList) == 0:
print("No content")
return
if self.createGroupId_cb.isChecked():
groupIds = dict()
currentId = 0
for proxy in proxyList:
if proxy.groupName not in groupIds:
groupIds[proxy.groupName] = currentId
currentId += 1
proxy.write_group_id(groupIds[proxy.groupName])
if anyAnimation:
if self.preroll.isChecked():
for frame in range(int(omAnim.MAnimControl.minTime().value), frameRange[0]):
om.MGlobal.viewFrame(frame)
for frame in range(frameRange[0], frameRange[1] + 1):
om.MGlobal.viewFrame(frame)
for item in proxyList:
if frame == frameRange[0]:
item.write_first_frame()
elif item.animation:
item.write_frame()
item.back_uv(self.bakeMesh, self.uvSetStr.text())
omAnim.MAnimControl.setCurrentTime(oldCurTime)
else:
for item in proxyList:
item.write_first_frame()
item.back_uv(self.bakeMesh, self.uvSetStr.text())
print("Data has been saved in %s, it took %.2f seconds." % (file_path[0], time.time() - startTime))
return file_path[0]
def fillWithSelectList(self):
self.contentList = []
selectionList = om.MGlobal.getActiveSelectionList()
contentList = []
for i in range(selectionList.length()):
dag_path = selectionList.getDagPath(i)
fnDepNode = om.MFnDependencyNode(dag_path.node())
itDag = om.MItDag()
# find xgen description
itDag.reset(fnDepNode.object(), om.MItDag.kDepthFirst, om.MFn.kNamedObject)
xgDes = None
while not itDag.isDone():
dn = om.MFnDependencyNode(itDag.currentItem())
if dn.typeName == 'xgmSplineDescription':
xgDes = dn
break
itDag.next()
if xgDes is not None:
contentList.append(
SaveXGenWindow.Content(xgDes, fnDepNode.name(), SaveXGenWindow.xgenType, fnDepNode.name(), False,
False, False, True))
boundMesh = self.findBoundMesh(xgDes)
if boundMesh is not None:
self.setBakeMesh(boundMesh)
continue
# find curve
itDag.reset(fnDepNode.object(), om.MItDag.kDepthFirst, om.MFn.kCurve)
isCurve = False
while not itDag.isDone():
isCurve = True
break
if isCurve:
groupNameStr: str = fnDepNode.name()
suffix = "_guide"
if groupNameStr.endswith(suffix):
groupNameStr = groupNameStr[:-len(suffix)]
contentList.append(
SaveXGenWindow.Content(fnDepNode, fnDepNode.name(), SaveXGenWindow.curveType, groupNameStr, True,
False, False, True))
self.table.setRowCount(len(contentList))
for row in range(len(contentList)):
self.table.setCellWidget(row, 0, contentList[row].export)
contentList[row].export.setStyleSheet("padding-left:8px")
item = QtWidgets.QTableWidgetItem(contentList[row].showName)
# item.setTextAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
item.setFlags(QtCore.Qt.ItemFlag.ItemIsEnabled)
self.table.setItem(row, 1, item)
item = QtWidgets.QTableWidgetItem(contentList[row].Type)
# item.setTextAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
item.setFlags(QtCore.Qt.ItemFlag.ItemIsEnabled)
self.table.setItem(row, 2, item)
self.table.setCellWidget(row, 3, contentList[row].groupName)
self.table.setCellWidget(row, 4, contentList[row].isGuide)
self.table.setCellWidget(row, 5, contentList[row].bakeUV)
self.table.setCellWidget(row, 6, contentList[row].animation)
self.contentList = contentList
def findBoundMesh(self, xgDes):
itDg = om.MItDependencyGraph(xgDes.object(), direction=om.MItDependencyGraph.kUpstream)
boundMesh = None
while not itDg.isDone():
dn = om.MFnDependencyNode(itDg.currentNode())
if dn.typeName == 'xgmSplineBase':
boundMeshPlug: om.MPlug = dn.findPlug('boundMesh', False)
boundMesh = om.MFnMesh(
om.MDagPath.getAPathTo(boundMeshPlug.elementByLogicalIndex(0).source().node()))
break
itDg.next()
return boundMesh
def setBakeMesh(self, mesh: om.MFnMesh):
if mesh is not None:
self.bakeMesh = mesh
self.MeshName.setText(f"Mesh: {mesh.name()}")
self.combo.clear()
self.combo.addItems(mesh.getUVSetNames())
# SaveXGenWindowInstanceName = '_SaveXGenWindowInstance'
# if SaveXGenWindowInstanceName not in globals():
# globals()[SaveXGenWindowInstanceName] = SaveXGenWindow()
# globals()[SaveXGenWindowInstanceName].show()
def run():
SaveXGenWindowInstanceName = '_SaveXGenWindowInstance'
if SaveXGenWindowInstanceName not in globals():
globals()[SaveXGenWindowInstanceName] = SaveXGenWindow()
globals()[SaveXGenWindowInstanceName].show()