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

1428 lines
56 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
import uuid
import xgenm as xg
import os
_XGenExporterVersion = "1.06"
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, keys):
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]]
outputs = {key: [] for key in keys}
for k, v in Items.items():
if k not in outputs:
continue
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)
outputs[k].append(PrimitiveInfos)
elif k in ('FaceUV', 'Positions', 'WIDTH_CV'):
for addr in v:
decompressed_data = decompressData(*addr)
outputs[k].append(array.array('f', decompressed_data))
elif k == 'FaceId':
for addr in v:
decompressed_data = decompressData(*addr)
outputs[k].append(array.array('i', decompressed_data))
return [outputs[k] for k in keys]
# %%
class AbcType:
string = (abcGeom.OStringGeomParam, abcGeom.OStringGeomParamSample)
int16 = (abcGeom.OInt16GeomParam, abcGeom.OInt16GeomParamSample)
int32 = (abcGeom.OInt32GeomParam, abcGeom.OInt32GeomParamSample)
int64 = (abcGeom.OInt64GeomParam, abcGeom.OInt64GeomParamSample)
color3f = (abcGeom.OC3fGeomParam, abcGeom.OC3fGeomParamSample)
float = (abcGeom.OFloatGeomParam, abcGeom.OFloatGeomParamSample)
vector2f = (abcGeom.OV2fGeomParam, abcGeom.OV2fGeomParamSample)
vector3f = (abcGeom.OV3fGeomParam, abcGeom.OV3fGeomParamSample)
# %%
class CurvesProxy:
def __init__(self, curveObj: abcGeom.OCurves, fnDepNode: om.MFnDependencyNode, needRootList=False, animation=False):
self.hairRootList = None
self.schema: abcGeom.OCurvesSchema = curveObj.getSchema()
self.cp: abc.OCompoundProperty = self.schema.getArbGeomParams()
self.needRootList = needRootList
self.animation = animation
self.firstSamp = abcGeom.OCurvesSchemaSample()
self.fnDepNode = fnDepNode
self.curves = None
self.groupName = None
self.is_guide = False
self.needBakeUV = False
def write_param(self, name: str, abcType, data, scope=abcGeom.GeometryScope.kUniformScope, extent=1):
if len(data) == 1:
scope = abcGeom.GeometryScope.kConstantScope
param = abcType[0](self.cp, name, False, scope, extent)
sample = abcType[1](data, scope)
param.set(sample)
def write_group_name(self, group_name: str, write_card_id=False):
group_name_data = list2ImathArray([group_name], imath.StringArray)
self.write_param('groom_group_name', AbcType.string, group_name_data)
if write_card_id:
self.write_param('groom_group_cards_id', AbcType.string, group_name_data)
self.groupName = group_name
def write_is_guide(self, is_guide=True):
self.isGuide = is_guide
if is_guide:
self.write_param('groom_guide', AbcType.int16, list2ImathArray([1], imath.ShortArray))
def write_group_id(self, group_id: int):
self.write_param('groom_group_id', AbcType.int32, 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.needRootList:
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.needRootList:
self.hairRootList.append(om.MPoint(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 bake_uv(self, bakeMesh: om.MFnMesh, uv_set: str = None):
if not self.needBakeUV or 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]
self.write_param('groom_root_uv', AbcType.vector2f, uvs)
# %%
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 FileSelectorWidget(QtWidgets.QWidget):
def __init__(self, callback):
super(FileSelectorWidget, self).__init__()
self.callback = callback
self.setup_ui()
def setup_ui(self):
self.layout = QtWidgets.QHBoxLayout(self)
self.file_line_edit = QtWidgets.QLineEdit(self)
self.layout.addWidget(self.file_line_edit)
self.browse_button = QtWidgets.QPushButton("Browse", self)
self.layout.addWidget(self.browse_button)
self.browse_button.clicked.connect(self.browse_file)
self.file_line_edit.textChanged.connect(self.callback)
def browse_file(self):
file_path = cmds.fileDialog2(fileMode=1, caption="Select a File")
if file_path:
self.file_line_edit.setText(file_path[0])
def set_file_path(self, path):
return self.file_line_edit.setText(path)
def get_file_path(self):
return self.file_line_edit.text()
# %%
SaveXGenDesWindowParentName = "_saveXGenDesWindow"
def getSaveXGenDesWindowParent():
sel = om.MSelectionList()
try:
sel.add(SaveXGenDesWindowParentName)
except:
trans = om.MFnTransform()
trans.create()
trans.setName(SaveXGenDesWindowParentName)
sel.add(SaveXGenDesWindowParentName)
return sel.getDagPath(0)
def deleteSaveXGenDesWindowParent():
if cmds.objExists(SaveXGenDesWindowParentName):
cmds.delete(SaveXGenDesWindowParentName)
# %%
def getExpressionPath(expr: str, pal_path, des_path, fx_name):
expr = expr.replace('${DESC}', xg.descriptionPath(pal_path, des_path)).replace('${FXMODULE}', fx_name)
ptex_path = None
if os.path.isdir(expr):
for f in os.listdir(expr):
if f.endswith('.ptx'):
ptex_path = f
if ptex_path is None:
return ""
return os.path.normpath(os.path.join(expr, ptex_path))
def getClumpingPtexPath(dn: om.MFnDependencyNode):
if not dn.object().hasFn(om.MFn.kTransform):
des_obj = om.MFnDagNode(dn.object()).parent(0)
else:
des_obj = dn.object()
des_path = str(om.MDagPath.getAPathTo(des_obj))
pal_path = str(om.MDagPath.getAPathTo(om.MFnDagNode(des_obj).parent(0)))
clumping = None
for fx in xg.fxModules(pal_path, des_path):
if fx.startswith('Clumping'):
clumping = fx
break
if clumping is None:
return ""
expr = xg.getAttr("mapDir", pal_path, des_path, clumping)
return getExpressionPath(expr, pal_path, des_path, clumping)
# %%
def generate_short_hash():
unique_id = uuid.uuid4()
return str(unique_id).replace('-', '')[:8]
def ConvertToInteractive(dn: om.MFnDependencyNode):
if not dn.object().hasFn(om.MFn.kTransform):
path = om.MDagPath.getAPathTo(om.MFnDagNode(dn.object()).parent(0))
else:
path = om.MDagPath.getAPathTo(dn.object())
cmds.select(path, replace=True)
res = cmds.xgmGroomConvert(prefix="z" + generate_short_hash())
if res is None:
raise Exception("Convert to interactive failed.")
sel: om.MSelectionList = om.MGlobal.getActiveSelectionList()
spline = om.MFnDagNode(sel.getDagPath(0))
om.MFnDagNode(getSaveXGenDesWindowParent()).addChild(spline.parent(0))
return spline
# return curve.parent(0)
# %%
import ctypes
from ctypes import c_void_p, c_uint64, c_ulonglong, c_float, c_int, c_char_p
PtexSamplerDllFuncName = "_PtexSamplerDllFunc"
class PtexSampler:
class DllFunc:
@staticmethod
def getVFunc(obj, index, *args):
vtble = ctypes.cast(obj, ctypes.POINTER(ctypes.c_void_p)).contents
vfuncAddr = ctypes.cast(vtble.value + index * 8, ctypes.POINTER(ctypes.c_void_p)).contents.value
return ctypes.CFUNCTYPE(*args)(vfuncAddr)
@staticmethod
def getPtexFilterEvelFunc(filter):
ptexFilterEvel = PtexSampler.DllFunc.getVFunc(filter, 2, c_void_p, c_void_p, c_void_p, c_int, c_int, c_int,
c_float, c_float,
c_float, c_float, c_float, c_float)
return ptexFilterEvel
@staticmethod
def getPtexTextureReleaseFunc(ptexTexture):
ptexTextureRelease = PtexSampler.DllFunc.getVFunc(ptexTexture, 1, c_void_p)
return ptexTextureRelease
class MyVector(ctypes.Structure):
_fields_ = [
("_Myfirst", ctypes.POINTER(ctypes.c_float)), # pointer to beginning of array
("_Mylast", ctypes.POINTER(ctypes.c_float)), # pointer to current end of sequence
("_Myend", ctypes.POINTER(ctypes.c_float)) # pointer to end of sequence
]
def __init__(self, size=10):
array = (ctypes.c_float * size)()
# 为每个指针分配内存
self._Myfirst = ctypes.cast(array, ctypes.POINTER(ctypes.c_float))
# _Mylast 初始化为数组的开始(指向和 _Myfirst 相同的位置)
self._Mylast = self._Myfirst # 当前结束位置指向数组的开始
_myend_address = ctypes.addressof(array) + ctypes.sizeof(array) # 获取数组末尾(地址)
self._Myend = ctypes.cast(_myend_address, ctypes.POINTER(ctypes.c_float))
def __getitem__(self, index):
return self._Myfirst[index]
def close(self):
getPtexTextureRelease = PtexSampler.DllFunc.getPtexTextureReleaseFunc(self.ptexTexture)
getPtexTextureRelease(self.ptexTexture)
def setupFilter(self, path):
DllFunc: PtexSampler.DllFunc = globals()[PtexSamplerDllFuncName]
err = ctypes.c_uint64()
ptexTexture = DllFunc.ptex_open(
path.encode(),
ctypes.byref(err), 0)
if ptexTexture is None:
raise Exception("no ptexTexture found")
self.ptexTexture = ctypes.c_void_p(ptexTexture)
# 定义Options结构体
class Options(ctypes.Structure):
_fields_ = [
("__structSize", ctypes.c_int), # (for internal use only)
("filter", ctypes.c_int), # Filter type.
("lerp", ctypes.c_bool), # Interpolate between mipmap levels.
("sharpness", ctypes.c_float), # Filter sharpness, 0..1 (for general bi-cubic filter only).
("noedgeblend", ctypes.c_bool) # Disable cross-face filtering.
]
def __init__(self, filter_=0, lerp_=False, sharpness_=0.0,
noedgeblend_=False): # Point-sampled (no filtering)
self.__structSize = ctypes.sizeof(Options) # 设置结构体大小
self.filter = filter_ # 设置过滤器类型
self.lerp = lerp_ # 设置是否插值
self.sharpness = sharpness_ # 设置过滤器锐度
self.noedgeblend = noedgeblend_ # 设置是否禁用跨面过滤
options = Options()
filter = DllFunc.ptex_getFilter(self.ptexTexture, options)
filter = ctypes.cast(filter, ctypes.c_void_p)
self.ptexFilterEvalFunc = DllFunc.getPtexFilterEvelFunc(filter)
self.vector = DllFunc.temp_vector
self.filter = filter
def __init__(self, path: str):
self.path = path
if PtexSamplerDllFuncName in globals():
self.setupFilter(path)
return
DllFunc = PtexSampler.DllFunc()
globals()[PtexSamplerDllFuncName] = DllFunc
ptex_dll = ctypes.cdll.LoadLibrary("Ptex.dll")
versions = ['2_2', '2_3', '2_4', '2_5', '2_6']
version = None
for _v in versions:
try:
Ptex_String_release = ptex_dll[f"??1String@v{_v}@Ptex@@QEAA@XZ"]
version = _v
break
except:
pass
if version is None:
raise Exception("Could not find correct Ptex version")
DllFunc.ptex_open = ptex_dll[f"?open@PtexTexture@v{version}@Ptex@@SAPEAV123@PEBDAEAVString@23@_N@Z"]
DllFunc.ptex_open.restype = ctypes.c_void_p
DllFunc.ptex_getFilter = ptex_dll[
f'?getFilter@PtexFilter@v{version}@Ptex@@SAPEAV123@PEAVPtexTexture@23@AEBUOptions@123@@Z']
DllFunc.ptex_getFilter.restype = ctypes.c_void_p
DllFunc.temp_vector = DllFunc.MyVector(10)
self.setupFilter(path)
def sampleData(self, faceU, faceV, faceId):
self.ptexFilterEvalFunc(self.filter, self.vector._Myfirst, 0, 3, faceId, faceU, faceV, 0, 0, 0, 0)
return self.vector[:3]
# %%
class XGenProxyEveryFrame(CurvesProxy):
def __init__(self, curveObj: abcGeom.OCurves, descFnDepNode: om.MFnDependencyNode, needRootList=False,
animation=False):
super().__init__(curveObj, None, needRootList, animation)
self.descFnDepNode = descFnDepNode
self.order_offset_map = None
def write_first_frame(self):
if print_debug:
startTime = time.time()
spline = ConvertToInteractive(self.descFnDepNode)
self.fnDepNode = spline
self.firstSpline = spline
PrimitiveInfosList, PositionsDataList, WidthsDataList, FaceIdList, FaceUVList = getXgenData(self.fnDepNode,
('PrimitiveInfos',
'Positions',
'WIDTH_CV',
'FaceId',
'FaceUV'))
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]
self.numCurves = numCurves
self.numCVs = numCVs
orders = imath.UnsignedCharArray(numCurves)
nVertices = imath.IntArray(numCurves)
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.needRootList:
self.hairRootList = []
knots = []
curveIndex = 0
cvIndex = 0
cvOffsets = imath.IntArray(numCurves)
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
cvOffsets[curveIndex] = cvIndex
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.needRootList:
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)] # The endpoint repeats one more than Maya
# print(knotsList)
knots += knotsList
curveIndex += 1
samp.setCurvesNumVertices(nVertices)
samp.setPositions(pointArray)
samp.setKnots(list2ImathArray(knots, imath.FloatArray))
samp.setOrders(orders)
widths = abcGeom.OFloatGeomParamSample(widthArray, abcGeom.GeometryScope.kVertexScope)
samp.setWidths(widths)
self.schema.set(samp)
if self.animation:
index2order = self.get_index2order(FaceIdList, FaceUVList)
self.order_offset_map = imath.IntArray(numCurves)
for i, offset in zip(index2order, cvOffsets):
self.order_offset_map[i] = offset
if print_debug:
# print(self.order_offset_map)
print("write_first_frame: %.4f" % (time.time() - startTime))
@staticmethod
def get_index2order(FaceIdList, FaceUVList):
order_list = []
for j in range(len(FaceIdList)):
FaceUVData = FaceUVList[j]
FaceIdData = FaceIdList[j]
for i, faceId in enumerate(FaceIdData):
u = FaceUVData[i * 2]
v = FaceUVData[i * 2 + 1]
order_list.append((faceId, u, v))
sorted_list = sorted((key, i) for i, key in enumerate(order_list))
index_list = imath.IntArray(len(order_list))
for order_index, item in enumerate(sorted_list):
my_index = item[1]
index_list[my_index] = order_index
# if print_debug:
# print(sorted_list)
return index_list
def write_frame(self):
if print_debug:
startTime = time.time()
spline = ConvertToInteractive(self.descFnDepNode)
self.fnDepNode = spline
PrimitiveInfosList, PositionsDataList, FaceIdList, FaceUVList = getXgenData(self.fnDepNode, ('PrimitiveInfos',
'Positions',
'FaceId',
'FaceUV'))
numCVs = self.numCVs
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())
if print_debug:
s = time.time()
index2order = self.get_index2order(FaceIdList, FaceUVList)
pointArray = imath.V3fArray(numCVs)
curveIndex = 0
for j in range(len(PrimitiveInfosList)):
PrimitiveInfos = PrimitiveInfosList[j]
posData = PositionsDataList[j]
for PrimitiveInfo in PrimitiveInfos:
offset = PrimitiveInfo[0]
length = int(PrimitiveInfo[1])
if length < 2:
continue
startAddr = offset * 3
cvIndex = self.order_offset_map[index2order[curveIndex]]
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
if print_debug:
print("loop: %.4f" % (time.time() - s))
samp.setPositions(pointArray)
self.schema.set(samp)
if print_debug:
print("write_frame: %.4f" % (time.time() - startTime))
# %%
GroomGuideIdStartIndexName = '_GroomGuideIdStartIndexName'
def getGroomGuideIdStartIndex():
return globals()[GroomGuideIdStartIndexName]
def setGroomGuideIdStartIndex(value=0):
globals()[GroomGuideIdStartIndexName] = value
# %%
class GuideProxy(CurvesProxy):
def __init__(self, curveObj: abcGeom.OCurves, fnDepNode: om.MFnDependencyNode, needRootList=False, animation=False):
if not fnDepNode.object().hasFn(om.MFn.kTransform):
fnDepNode = om.MFnDependencyNode(om.MFnDagNode(fnDepNode.object()).parent(0))
super().__init__(curveObj, fnDepNode, needRootList, animation)
itDag = om.MItDag()
itDag.reset(self.fnDepNode.object(), om.MItDag.kBreadthFirst, om.MFn.kInvalid)
guides = []
while not itDag.isDone():
dn = om.MFnDependencyNode(itDag.currentItem())
if dn.typeName == 'xgmSplineGuide':
guides.append(itDag.getPath())
itDag.next()
self.guides: list[om.MDagPath] = guides
self.xgenProxy = None
self.ptexPath = None
self.writePtexGuideId = False
def set_xgen_proxy_and_ptex(self, xgenSpline: XGenProxyEveryFrame, ptexPath: str):
self.xgenProxy = xgenSpline
self.ptexPath = ptexPath
def write_guide_id_from_ptex(self):
if not self.writePtexGuideId:
return
ptexPath = self.ptexPath
xgenSpline = self.xgenProxy
if ptexPath is None or ptexPath == "":
return
if self.xgenProxy == None:
return
ptexSampler = PtexSampler(ptexPath)
self.regionPtex = ptexPath
guide_map = dict()
def color2Int(color):
a = int(color[0] * 255) & 0xff
b = int(color[1] * 255) & 0xff
c = int(color[2] * 255) & 0xff
return (a << 16) | (b << 8) | c
for i, guide in enumerate(self.guides):
dn = om.MFnDependencyNode(guide.node())
u = dn.findPlug('uLoc', False).asFloat()
v = dn.findPlug('vLoc', False).asFloat()
faceId = dn.findPlug('faceId', False).asInt()
color = ptexSampler.sampleData(u, v, faceId)
hash = color2Int(color)
if hash in guide_map:
old_guide = om.MFnDependencyNode(guide_map[hash][0].node())
print(
f"guide {dn.name()} and {old_guide.name()} are in the same area on texture, only use {old_guide.name()}.")
continue
guide_map[hash] = (guide, i)
FaceUVList, FaceIdList = getXgenData(xgenSpline.firstSpline, ('FaceUV', 'FaceId'))
guideIdStartIndex = getGroomGuideIdStartIndex()
guideIdNextStartIndex = guideIdStartIndex + len(self.guides)
groom_id_data = list2ImathArray(list(range(guideIdStartIndex, guideIdNextStartIndex)), imath.IntArray)
self.write_param("groom_id", AbcType.int32, groom_id_data)
spline_num = len(xgenSpline.hairRootList)
weight_data = list2ImathArray([1.0] * spline_num, imath.FloatArray)
xgenSpline.write_param("groom_guide_weights", AbcType.float, weight_data)
guide_id_data = imath.IntArray(spline_num)
first_guide_name = om.MFnDependencyNode(self.guides[0].node()).name()
spline_index = 0
for j in range(len(FaceIdList)):
FaceUVData = FaceUVList[j]
FaceIdData = FaceIdList[j]
# print(len(FaceIdData),len(FaceUVData))
for i, faceId in enumerate(FaceIdData):
u = FaceUVData[i * 2]
v = FaceUVData[i * 2 + 1]
color = ptexSampler.sampleData(u, v, faceId)
hash = color2Int(color)
guide_id = guideIdStartIndex
if hash not in guide_map:
print(f"The spline index ({j} ,{i}) does not have a valid guide attached to {first_guide_name}.")
else:
guide_id += guide_map[hash][1]
if spline_index >= spline_num:
raise Exception("spline_index >= spline_num")
guide_id_data[spline_index] = guide_id
# guide_map[hash][2].append(spline_index)
spline_index += 1
# print(guide_map)
ptexSampler.close()
xgenSpline.write_param("groom_closest_guides", AbcType.int32, guide_id_data)
setGroomGuideIdStartIndex(guideIdNextStartIndex)
def write_first_frame(self):
numCurves = len(self.guides)
orders = imath.IntArray(numCurves)
nVertices = imath.IntArray(numCurves)
pointslist = []
knots = []
if self.needRootList:
self.hairRootList = []
samp = self.firstSamp
samp.setBasis(abcGeom.BasisType.kBsplineBasis)
samp.setWrap(abcGeom.CurvePeriodicity.kNonPeriodic)
samp.setType(abcGeom.CurveType.kLinear)
degree = 1
for i in range(numCurves):
data = cmds.xgmGuideGeom(guide=self.guides[i], numVertices=True)
numCVs = int(data[0])
data = cmds.xgmGuideGeom(guide=self.guides[i], controlPoints=True)
pointslist += data
orders[i] = degree + 1
nVertices[i] = numCVs
if self.needRootList:
self.hairRootList.append(om.MPoint(data[:3]))
knotsInsideNum = numCVs - degree + 1
knotsList = [*([0] * degree), *list(range(knotsInsideNum)),
*([knotsInsideNum - 1] * degree)] # The endpoint repeats one more than Maya
# print(knotsList)
knots += knotsList
samp.setCurvesNumVertices(nVertices)
samp.setPositions(floatList2V3fArray(pointslist))
samp.setOrders(list2ImathArray(orders, imath.UnsignedCharArray))
samp.setKnots(list2ImathArray(knots, imath.FloatArray))
self.schema.set(samp)
def write_frame(self):
numCurves = len(self.guides)
if numCurves == 0:
return
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):
data = cmds.xgmGuideGeom(guide=self.guides[i], controlPoints=True)
pointslist += data
samp.setPositions(floatList2V3fArray(pointslist))
self.schema.set(samp)
# %%
class SaveXGenDesWindow(QtWidgets.QDialog):
class MultiSelectCheckBox(QtWidgets.QCheckBox):
def __init__(self, column_name, parent=None):
super().__init__(parent)
self.column_name = column_name
self.clicked.connect(self.on_clicked)
def on_clicked(self, checked):
window = self.find_window()
if not window or not hasattr(window, 'table'):
return
table = window.table
contents = window.contentList
selected_rows = self.get_rows_to_changing(table)
for row in selected_rows:
if 0 <= row < len(contents):
content = contents[row]
checkbox = getattr(content, self.column_name)
if checkbox is not None:
checkbox.blockSignals(True)
checkbox.setChecked(checked)
checkbox.blockSignals(False)
def find_window(self):
parent = self.parent()
while parent:
if (isinstance(parent, SaveXGenDesWindow) or
parent.__class__.__name__ == 'SaveXGenDesWindow'):
return parent
parent = parent.parent()
return None
def get_rows_to_changing(self, table):
pos = self.mapTo(table.viewport(), QtCore.QPoint(0, 0))
_index = table.indexAt(pos).row()
selected_rows = [index.row() for index in table.selectionModel().selectedRows()]
return selected_rows if _index in selected_rows else [_index]
class Content:
def __init__(self, fnDepNode, showName, groupName, useGuide, bakeUV, animation, export):
self.showName = showName
self.fnDepNode = fnDepNode
self.groupName = QtWidgets.QLineEdit()
self.groupName.setText(groupName)
self.useGuide = SaveXGenDesWindow.MultiSelectCheckBox("useGuide")
self.useGuide.setChecked(useGuide)
self.bakeUV = SaveXGenDesWindow.MultiSelectCheckBox("bakeUV")
self.bakeUV.setChecked(bakeUV)
self.animation = SaveXGenDesWindow.MultiSelectCheckBox("animation")
self.animation.setChecked(animation)
self.export = SaveXGenDesWindow.MultiSelectCheckBox("export")
self.export.setChecked(export)
self.splineAnimation = False
self.writePtexGuideId = False
self.regionPtex = ""
def __init__(self, parent=mayaWindow()):
super(SaveXGenDesWindow, self).__init__(parent)
self.contentList: List[SaveXGenDesWindow.Content] = []
self.save_path = '.'
self.bakeMesh = None
self.setWindowTitle("Export XGen description to UE Groom v{}".format(_XGenExporterVersion))
self.setGeometry(400, 400, 1130, 550)
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("Select XGen Description")
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.fillTableWithSelectList)
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(7)
self.table.setHorizontalHeaderLabels(["", "Name", "Group name", "Use guide", "Bake UV", "Animation", ""])
self.table.horizontalHeader().setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
self.table.setColumnWidth(0, 40)
self.table.setColumnWidth(3, 140)
self.table.horizontalHeader().setSectionResizeMode(3, QtWidgets.QHeaderView.Fixed)
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.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) # Multi Selection
self.table.setSelectionBehavior(QtWidgets.QTableView.SelectRows)
self.table.setStyleSheet("""
QTableView::item
{
border: 0px;
padding: 5px;
background-color: rgb(68, 68, 68);
}
QTableView::item:selected {
background-color: rgb(81, 133, 166);
}
QTableView::item QCheckBox {
padding-left:60px;
}
""")
self.table.clearContents()
self.table.setRowCount(0)
self.splitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal)
# self.table.cellClicked.connect(self.update_detail)
self.table.selectionModel().selectionChanged.connect(self.update_detail)
self.splitter.addWidget(self.table)
# Detail view on the right
self.detail_widget = None
self.clear_detail()
# self.splitter.setSizes([1,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_uvset_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)
self.createCardId_cb = QtWidgets.QCheckBox("Create card id same as group name")
self.createCardId_cb.setChecked(False)
hBox.addWidget(self.createCardId_cb)
frameLayout.addLayout(hBox)
self.save_button = QtWidgets.QPushButton("Save Alembic File", self)
self.save_button.clicked.connect(self.save_abc)
self.clear_temp_button = QtWidgets.QPushButton("Clear Temp Data", self)
self.clear_temp_button.clicked.connect(self.clear_temp)
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.clear_temp_button)
button_layout.addWidget(self.cancel_button)
main_layout.addWidget(self.splitter)
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 clear_detail(self):
old_sizes = None
if self.detail_widget is not None:
old_sizes = self.splitter.sizes()
self.detail_widget.setParent(None)
self.detail_label = QtWidgets.QLabel("Select an item to view details")
self.detail_label.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft)
self.detail_widget = QtWidgets.QWidget()
self.detail_layout = QtWidgets.QVBoxLayout()
self.detail_layout.addWidget(self.detail_label)
self.detail_widget.setLayout(self.detail_layout)
self.splitter.addWidget(self.detail_widget)
# self.detail_widget.setMaximumWidth(1000)
if old_sizes is None:
self.splitter.setStretchFactor(0, 4)
self.splitter.setStretchFactor(1, 1)
else:
self.splitter.setSizes(old_sizes)
def create_detail_checkBox(self, prop):
selected_rows = [index.row() for index in self.table.selectionModel().selectedRows()]
if len(selected_rows) == 0:
return None
checkBox = QtWidgets.QCheckBox(self)
isTrue = getattr(self.contentList[selected_rows[0]],prop)
checkBox.setChecked(isTrue)
for row in selected_rows:
content = self.contentList[row]
if getattr(content,prop) != isTrue:
checkBox.setCheckState(QtCore.Qt.CheckState.PartiallyChecked)
break
def onStateChange(state):
for row in selected_rows:
content = self.contentList[row]
setattr(content, prop, state == 2)
checkBox.stateChanged.connect(onStateChange)
return checkBox
def update_detail(self, indices):
self.clear_detail()
selected_rows = [index.row() for index in self.table.selectionModel().selectedRows()]
if len(selected_rows) == 0:
return
content = self.contentList[selected_rows[0]]
is_multi_selected = len(self.table.selectionModel().selectedRows()) > 1
vBox = QtWidgets.QVBoxLayout()
self.detail_layout.addLayout(vBox)
vBox2 = QtWidgets.QVBoxLayout()
self.detail_layout.addLayout(vBox2)
vBox2.addStretch(1)
self.detail_label.setText(content.showName if not is_multi_selected else "--")
self.detail_label.setStyleSheet('font-weight:bold;margin-bottom:20px')
self.detail_label.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignCenter) # 设置对齐方式
write_spline_animation = self.create_detail_checkBox('splineAnimation')
write_spline_animation.setText("write spline animation")
write_spline_animation.setToolTip(
"If writes animation, also write spline animation, not just the animation of guides.")
vBox.addWidget(write_spline_animation)
write_guide_id_cb = self.create_detail_checkBox('writePtexGuideId')
write_guide_id_cb.setText("write guide id from ptex")
write_guide_id_cb.setToolTip(
"Experimental feature, only supports versions up to UE5.3, writes properties such as groom_closest_guides.")
# vBox.setContentsMargins(0,0,10,10)
vBox.addWidget(write_guide_id_cb)
if not is_multi_selected:
label = QtWidgets.QLabel('Select .ptx file:')
# label.setStyleSheet('font-size:16px')
vBox.addWidget(label)
def setPath(text):
content.regionPtex = text
RegionPtex = FileSelectorWidget(setPath)
RegionPtex.set_file_path(content.regionPtex)
vBox.addWidget(RegionPtex)
self.splitter.addWidget(self.detail_widget)
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_uvset_label(self):
selected_option = self.combo.currentText()
self.uvSetStr.setText(selected_option)
def clear_temp(self):
deleteSaveXGenDesWindowParent()
def save_abc(self):
if len(self.contentList) == 0:
print("No content")
return
file_path = cmds.fileDialog2(
caption="Save Alembic File",
fileMode=0,
okCaption="save",
startingDirectory=self.save_path,
ff='Alembic Files (*.abc);;All Files (*)'
)
if file_path:
self.save_path = file_path[0]
else:
return
selectionList = om.MGlobal.getActiveSelectionList()
startTime = time.time()
oldCurTime = omAnim.MAnimControl.currentTime()
archive = abc.OArchive(file_path[0])
anyAnimation = False
for item in self.contentList:
if item.export.isChecked():
hasAnimation = item.animation.isChecked()
if hasAnimation:
anyAnimation = True
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 = [] # All Alembic content should be destroyed at the end of the method, otherwise it will not be written to the file
setGroomGuideIdStartIndex(0)
for item in self.contentList:
if item.export.isChecked():
fnDepNode = item.fnDepNode
needBakeUV = item.bakeUV.isChecked()
hasAnimation = item.animation.isChecked()
useGuide = item.useGuide.isChecked()
if hasAnimation:
curveObj = abcGeom.OCurves(archive.getTop(), fnDepNode.name(), timeIndex)
else:
curveObj = abcGeom.OCurves(archive.getTop(), fnDepNode.name())
xgenProxy = XGenProxyEveryFrame(curveObj, item.fnDepNode, needBakeUV | useGuide, item.splineAnimation)
xgenProxy.needBakeUV = needBakeUV
xgenProxy.write_group_name(item.groupName.text(), self.createCardId_cb.isChecked())
if useGuide:
# guides = GuidesToCurves(item.fnDepNode)
guideName = fnDepNode.name() + "_guide"
if hasAnimation:
curveObj = abcGeom.OCurves(archive.getTop(), guideName, timeIndex)
else:
curveObj = abcGeom.OCurves(archive.getTop(), guideName)
guideProxy = GuideProxy(curveObj, fnDepNode, False, hasAnimation)
guideProxy.write_group_name(item.groupName.text(), self.createCardId_cb.isChecked())
guideProxy.write_is_guide(True)
proxyList.append(guideProxy)
guideProxy.set_xgen_proxy_and_ptex(xgenProxy, item.regionPtex)
guideProxy.writePtexGuideId = item.writePtexGuideId
proxyList.append(xgenProxy) # after guides, for baking
# return
if len(proxyList) == 0:
print("No content")
om.MGlobal.setActiveSelectionList(selectionList)
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()
omAnim.MAnimControl.setCurrentTime(oldCurTime)
else:
for item in proxyList:
item.write_first_frame()
for item in proxyList:
item.bake_uv(self.bakeMesh, self.uvSetStr.text())
if isinstance(item, GuideProxy):
item.write_guide_id_from_ptex()
print("Data has been saved in %s, it took %.2f seconds." % (file_path[0], time.time() - startTime))
om.MGlobal.setActiveSelectionList(selectionList)
return file_path[0]
def fillTableWithSelectList(self):
self.clear_detail()
self.contentList = []
selectionList = om.MGlobal.getActiveSelectionList()
contentList = []
for i in range(selectionList.length()):
dag_path = selectionList.getDagPath(i)
fnDepNode = om.MFnDependencyNode(dag_path.node())
if fnDepNode.typeName == 'xgmPalette':
continue
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 == 'xgmDescription':
xgDes = dn
break
itDag.next()
if xgDes is not None:
content = SaveXGenDesWindow.Content(xgDes, fnDepNode.name(), fnDepNode.name(), True,
False, False, True)
content.regionPtex = getClumpingPtexPath(fnDepNode)
contentList.append(content)
boundMesh = self.findBoundMesh(xgDes)
if boundMesh is not None:
self.setBakeMesh(boundMesh)
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.setFlags(QtCore.Qt.ItemFlag.ItemIsEnabled | QtCore.Qt.ItemFlag.ItemIsSelectable)
self.table.setItem(row, 1, item)
self.table.setCellWidget(row, 2, contentList[row].groupName)
self.table.setCellWidget(row, 3, contentList[row].useGuide)
self.table.setCellWidget(row, 4, contentList[row].bakeUV)
self.table.setCellWidget(row, 5, contentList[row].animation)
self.contentList = contentList
def findBoundMesh(self, xgDes):
itDg = om.MItDag()
itDg.reset(om.MFnDagNode(xgDes.object()).parent(0), om.MItDag.kDepthFirst, om.MFn.kPluginShape)
boundMesh = None
while not itDg.isDone():
dn = om.MFnDependencyNode(itDg.currentItem())
if dn.typeName == 'xgmSubdPatch':
boundMeshPlug: om.MPlug = dn.findPlug('geometry', False)
boundMesh = om.MFnMesh(
om.MDagPath.getAPathTo(boundMeshPlug.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())
# SaveXGenDesWindowInstanceName = '_SaveXGenDesWindowInstance'
# if SaveXGenDesWindowInstanceName not in globals():
# globals()[SaveXGenDesWindowInstanceName] = SaveXGenDesWindow()
# globals()[SaveXGenDesWindowInstanceName].show()
# # SaveXGenDesWindow().show()
def run():
SaveXGenDesWindowInstanceName = '_SaveXGenDesWindowInstance'
if SaveXGenDesWindowInstanceName not in globals():
globals()[SaveXGenDesWindowInstanceName] = SaveXGenDesWindow()
globals()[SaveXGenDesWindowInstanceName].show()
# SaveXGenDesWindow().show()