def parseActor(unrtext, safe=False): """ parse UnrealText of a single Actor into an ObjectInfo representation. :param unrtext: the unreal text for the level :param safe: the first line in unrtext is the Begin Actor line and the End Actor line is not present :return: instance of :class:`m2u.helper.ObjectInfo.ObjectInfo` .. note: if you provide text with more than one Actor in it, only the first Actor will be converted. If you have a multi-selection, use :func:`parseActors` """ # to keep it simple we currently only get the entries we are interested # in and pile everything else up to a text, so we do no sub-object parsing # this may change in the future! sindex = 0 # split every line and erase leading whitespaces, removes empty lines lines = re.split("\n+\s*", unrtext) # find the first line that begins the Actor (most likely the first line) if not safe: # no preprocessing was done, most likely the third line then for i in range(len(lines)): if lines[i].startswith("Begin Actor"): sindex = i break g = re.search("Class=(.+?) Name=(.+?)\s+", lines[sindex]) if not g: # no name? invalid text, obviously _lg.error( "no name and type found for object") return None objtype = g.group(1) objname = g.group(2) objtypecommon = getCommonTypeFromInternal(objtype) objInfo = ObjectInfo(objname, objtype, objtypecommon) textblock = "" for line in lines[sindex+1:]: # add jumping over sub-object groups (skip lines inbetween or so) # if line startswith "Begin Object" # dumb copy lines until # line startswith "End Object" is found # keep track of depth (begin obj,begin obj, end obj ->) if not safe and line.startswith("End Actor"): break # done reading actor elif line.startswith("Location="): objInfo.position = _getFloatTuple(line) elif line.startswith("Rotation="): rot = _getFloatTuple(line) objInfo.rotation = _convertRotationFromUDK(rot) elif line.startswith("DrawScale3D="): objInfo.scale = _getFloatTuple(line) else: textblock += ("\n"+line) objInfo.attrs["textblock"]=textblock return objInfo
def parseActor(unrtext, safe=False): """ parse UnrealText of a single Actor into an ObjectInfo representation. :param unrtext: the unreal text for the level :param safe: the first line in unrtext is the Begin Actor line and the End Actor line is not present :return: instance of :class:`m2u.helper.ObjectInfo.ObjectInfo` .. note: if you provide text with more than one Actor in it, only the first Actor will be converted. If you have a multi-selection, use :func:`parseActors` """ # to keep it simple we currently only get the entries we are interested # in and pile everything else up to a text, so we do no sub-object parsing # this may change in the future! sindex = 0 # split every line and erase leading whitespaces, removes empty lines lines = re.split("\n+\s*", unrtext) # find the first line that begins the Actor (most likely the first line) if not safe: # no preprocessing was done, most likely the third line then for i in range(len(lines)): if lines[i].startswith("Begin Actor"): sindex = i break g = re.search("Class=(.+?) Name=(.+?)\s+", lines[sindex]) if not g: # no name? invalid text, obviously _lg.error("no name and type found for object") return None objtype = g.group(1) objname = g.group(2) objtypecommon = getCommonTypeFromInternal(objtype) objInfo = ObjectInfo(objname, objtype, objtypecommon) textblock = "" for line in lines[sindex + 1:]: # add jumping over sub-object groups (skip lines inbetween or so) # if line startswith "Begin Object" # dumb copy lines until # line startswith "End Object" is found # keep track of depth (begin obj,begin obj, end obj ->) if not safe and line.startswith("End Actor"): break # done reading actor elif line.startswith("Location="): objInfo.position = _getFloatTuple(line) elif line.startswith("Rotation="): rot = _getFloatTuple(line) objInfo.rotation = _convertRotationFromUDK(rot) elif line.startswith("DrawScale3D="): objInfo.scale = _getFloatTuple(line) else: textblock += ("\n" + line) objInfo.attrs["textblock"] = textblock return objInfo
def sendSelectedToEd(): """ there is the special case where there is one type of mesh in the scene with edited geometry but an AssetPath pointing to the unedited file if the user wants that object to be exported as a new asset and don't overwrite the existing asset, (our automation couldn't detect that, because all assets with same path have same geometry) the user would have to call sendSelectedAsNew in that case? Then we would make sure that the user is provided with the UI to change the assetPath of those objects on export. If the user does not choose sendAsNew, we assume he wants to overwrite any files on disk for sure. There is also the case where we don't want to export objects that are already in the editor, because reimporting takes time, if we didn't change the asset, don't ask. Also, we maybe don't want to overwrite files that already are on disk, because that takes time. We only want to create the map from the objects we have and add those objects that we don't have. So we need a third option "sendSelectedAddMissingOnly" We might intelligently do a check on file-date, import-date in editor and maybe a import-date in program for one or the other case to determine if a reimport or a file overwrite is necessary or not What is the "AssetPath" supposed to be? It is the file path, including the file-extension, relative to the current projects Art-Source folder. No absolute paths, but that is depending on the actual pipeline-implementation, since all functions that deal with file paths will be delegated to a pipeline module, and that may be replaced by the user. 1. get selected objects 2. for each object, get the "AssetPath" attribute 3. if the object has no such attribute, save it in a untagged list if it has, save it in a tagged list entry (filepath:[objects]) 4. for each object in the tagged list, check if the file exists on disk fancy: check each object with same file if the gemometry is same - if not, tell user that objects with same file path differ and that he should export as new. - do that by adding all objects with same geometry to a list - then ask the user how to rename that asset - replace the assetPath value on all assets in that list and add them to the tagged unique list 5. if it does not, export one of the associated objects as asset (overwrite any file found) 6. for each object in the untagged list fancy: check all other objects in the untagged list if the geometry is same for each where it is same, insert in an association list (firstObject:[matchingObjects]) fancy2: ask the user if the "unique" objects as found are ok, if not, he has to split objects into a new "unique" group for each first object in that association list check if the geometry is the same as in any of the tagged list uniques if it is, set the AssetPath attrib on all matching objects to that of the unique if it is not, export it as a new Asset and set the AssetPath attrib on all matching objects add all those to the tagged association list 7. for each unique object in the tagged list tell the editor to import the associated file 8. for each selected object tell the editor to add an actor with the AssetPath as asset extend to send lights and stuff like that this function will perform something between O(n) and O(n^2) would need to analyze this a little more only counting objects... vertex count may be different in scenes, and the heavier the vert count, again the heavier the check will be... """ # 1. get selected objects (only transform nodes) selectedObjects = pm.selected(type="transform") # filter out transforms that don't have a mesh-shape node selectedMeshes = list() for obj in selectedObjects: meshShapes = pm.listRelatives(obj, shapes=True, type="mesh") if len(meshShapes) > 0: selectedMeshes.append(obj) _lg.debug("found %i selected meshes" % len(selectedMeshes)) # TODO: maybe filter other transferable stuff like lights or so # 2. for each object get the "AssetPath" attribute untaggedList = list() taggedDict = {} for obj in selectedMeshes: if obj.hasAttr("AssetPath"): assetPathAttr = obj.attr("AssetPath") assetPathValue = assetPathAttr.get() if len(assetPathValue) > 0: taggedDict.setdefault(assetPathValue, []).append(obj) else: # if the ass path is empty, that is equal to the attr not being there untaggedList.append(obj) else: # unknown asset, we will handle those later untaggedList.append(obj) _lg.debug("found %i untagged" % len(untaggedList)) _lg.debug("found %i tagged" % len(taggedDict)) # 3. do the geometry check for tagged objects # this assembles the taggedUniqueDict taggedDiscrepancyDetected = False taggedUniqueDict = {} for lis in taggedDict.values(): # for obj in lis: #we modify lis, so iterator won't work while len(lis) > 0: # use while instead obj = lis[0] taggedUniqueDict[obj] = [] # compare this object against all others in the list. for otherObj in lis[1:]: if 0 == pm.polyCompare(obj, otherObj, vertices=True): # if the geometry matches, add the other to the unique list # with this object as key, and remove from the old list taggedUniqueDict[obj].append(otherObj) lis.remove(otherObj) else: taggedDiscrepancyDetected = True lis.remove(obj) # we are done with this object too _lg.debug("found %i tagged uniques" % len(taggedUniqueDict)) # 3. do the geometry check for untagged objects untaggedUniquesDetected = False while len(untaggedList) > 0: obj = untaggedList[0] if not obj.hasAttr("AssetPath"): pm.addAttr(obj.name(), longName="AssetPath", dataType="string", keyable=False) foundUniqueForMe = False # compare against one of the tagged uniques for other in taggedUniqueDict.keys(): # if that geometry matches, we found the unique for this obj if 0 == pm.polyCompare(obj, other, vertices=True): taggedUniqueDict[other].append(obj) # set "AssetPath" attr to match that of the unique obj.attr("AssetPath").set(other.attr("AssetPath").get()) # we are done with this object untaggedList.remove(obj) foundUniqueForMe = True _lg.debug("found a unique key (%s) for %s" % (other.name(), obj.name())) break if not foundUniqueForMe: untaggedUniquesDetected = True # make this a new unique, simply take the objects name as AssetPath npath = obj.shortName() + "_AutoExport" + ".fbx" obj.attr("AssetPath").set(npath) taggedUniqueDict[obj] = [] untaggedList.remove(obj) _lg.debug("assuming new untagged unique: " + obj.shortName()) # we will automatically compare to all other untagged to find # members for our new unique in the next loop iteration _lg.debug("found %i uniques (with untagged)" % len(taggedUniqueDict)) # TODO: 4. UI-stuff... # 4. if taggedDiscrepancy or untaggedUniques were detected, # list all uniques in the UI and let the user change names # force him to change names for taggedUniques with same AssetPath of course # if taggedDiscrepancyDetected: # for unique in taggedUniqueDict.keys(): # it is up to the UI to do that and let the user # set a new assetPath on any of those unique guy's lists # 5. export files stuff for obj in taggedUniqueDict.keys(): exportObjectAsAsset(obj.name(), obj.attr("AssetPath").get()) # 6. tell the editor to import all the uniques fileList = [] for obj in taggedUniqueDict.keys(): fileList.append(obj.attr("AssetPath").get()) m2u.core.getEditor().importAssetsBatch(fileList) # 7. tell the editor to assemble the scene objInfoList = [] for obj in selectedMeshes: # TODO: make that a new function objInfo = ObjectInfo(name=obj.shortName(), typeInternal="mesh", typeCommon="mesh") objTransforms = m2u.maya.mayaObjectTracker.getTransformationFromObj(obj) objInfo.pos = objTransforms[0] objInfo.rot = objTransforms[1] objInfo.scale = objTransforms[2] objInfo.AssetPath = obj.attr("AssetPath").get() objInfoList.append(objInfo) m2u.core.getEditor().addActorBatch(objInfoList)
def sendSelectedToEd(): """ there is the special case where there is one type of mesh in the scene with edited geometry but an AssetPath pointing to the unedited file if the user wants that object to be exported as a new asset and don't overwrite the existing asset, (our automation couldn't detect that, because all assets with same path have same geometry) the user would have to call sendSelectedAsNew in that case? Then we would make sure that the user is provided with the UI to change the assetPath of those objects on export. If the user does not choose sendAsNew, we assume he wants to overwrite any files on disk for sure. There is also the case where we don't want to export objects that are already in the editor, because reimporting takes time, if we didn't change the asset, don't ask. Also, we maybe don't want to overwrite files that already are on disk, because that takes time. We only want to create the map from the objects we have and add those objects that we don't have. So we need a third option "sendSelectedAddMissingOnly" We might intelligently do a check on file-date, import-date in editor and maybe a import-date in program for one or the other case to determine if a reimport or a file overwrite is necessary or not What is the "AssetPath" supposed to be? It is the file path, including the file-extension, relative to the current projects Art-Source folder. No absolute paths, but that is depending on the actual pipeline-implementation, since all functions that deal with file paths will be delegated to a pipeline module, and that may be replaced by the user. 1. get selected objects 2. for each object, get the "AssetPath" attribute 3. if the object has no such attribute, save it in a untagged list if it has, save it in a tagged list entry (filepath:[objects]) 4. for each object in the tagged list, check if the file exists on disk fancy: check each object with same file if the gemometry is same - if not, tell user that objects with same file path differ and that he should export as new. - do that by adding all objects with same geometry to a list - then ask the user how to rename that asset - replace the assetPath value on all assets in that list and add them to the tagged unique list 5. if it does not, export one of the associated objects as asset (overwrite any file found) 6. for each object in the untagged list fancy: check all other objects in the untagged list if the geometry is same for each where it is same, insert in an association list (firstObject:[matchingObjects]) fancy2: ask the user if the "unique" objects as found are ok, if not, he has to split objects into a new "unique" group for each first object in that association list check if the geometry is the same as in any of the tagged list uniques if it is, set the AssetPath attrib on all matching objects to that of the unique if it is not, export it as a new Asset and set the AssetPath attrib on all matching objects add all those to the tagged association list 7. for each unique object in the tagged list tell the editor to import the associated file 8. for each selected object tell the editor to add an actor with the AssetPath as asset extend to send lights and stuff like that this function will perform something between O(n) and O(n^2) would need to analyze this a little more only counting objects... vertex count may be different in scenes, and the heavier the vert count, again the heavier the check will be... """ #1. get selected objects (only transform nodes) selectedObjects = pm.selected(type="transform") # filter out transforms that don't have a mesh-shape node selectedMeshes = list() for obj in selectedObjects: meshShapes = pm.listRelatives(obj, shapes=True, type="mesh") if len(meshShapes)>0: selectedMeshes.append(obj) _lg.debug("found %i selected meshes" % len(selectedMeshes)) # TODO: maybe filter other transferable stuff like lights or so #2. for each object get the "AssetPath" attribute untaggedList = list() taggedDict = {} for obj in selectedMeshes: if obj.hasAttr("AssetPath"): assetPathAttr = obj.attr("AssetPath") assetPathValue = assetPathAttr.get() if len(assetPathValue)>0: taggedDict.setdefault(assetPathValue,[]).append(obj) else: # if the ass path is empty, that is equal to the attr not being there untaggedList.append(obj) else: # unknown asset, we will handle those later untaggedList.append(obj) _lg.debug("found %i untagged" % len(untaggedList)) _lg.debug("found %i tagged" % len(taggedDict)) #3. do the geometry check for tagged objects # this assembles the taggedUniqueDict taggedDiscrepancyDetected = False taggedUniqueDict = {} for lis in taggedDict.values(): #for obj in lis: #we modify lis, so iterator won't work while len(lis)>0: # use while instead obj = lis[0] taggedUniqueDict[obj]=[] # compare this object against all others in the list. for otherObj in lis[1:]: if 0 == pm.polyCompare(obj, otherObj, vertices=True): # if the geometry matches, add the other to the unique list # with this object as key, and remove from the old list taggedUniqueDict[obj].append(otherObj) lis.remove(otherObj) else: taggedDiscrepancyDetected = True lis.remove(obj) # we are done with this object too _lg.debug("found %i tagged uniques" % len(taggedUniqueDict)) #3. do the geometry check for untagged objects untaggedUniquesDetected = False while len(untaggedList)>0: obj = untaggedList[0] if not obj.hasAttr("AssetPath"): pm.addAttr(obj.name(), longName="AssetPath", dataType="string", keyable=False) foundUniqueForMe = False # compare against one of the tagged uniques for other in taggedUniqueDict.keys(): # if that geometry matches, we found the unique for this obj if 0 == pm.polyCompare(obj, other, vertices=True): taggedUniqueDict[other].append(obj) # set "AssetPath" attr to match that of the unique obj.attr("AssetPath").set(other.attr("AssetPath").get()) # we are done with this object untaggedList.remove(obj) foundUniqueForMe = True _lg.debug("found a unique key (%s) for %s" %(other.name(), obj.name())) break if not foundUniqueForMe: untaggedUniquesDetected = True # make this a new unique, simply take the objects name as AssetPath npath = obj.shortName() + "_AutoExport" + ".fbx" obj.attr("AssetPath").set(npath) taggedUniqueDict[obj]=[] untaggedList.remove(obj) _lg.debug("assuming new untagged unique: "+obj.shortName()) # we will automatically compare to all other untagged to find # members for our new unique in the next loop iteration _lg.debug("found %i uniques (with untagged)" % len(taggedUniqueDict)) # TODO: 4. UI-stuff... #4. if taggedDiscrepancy or untaggedUniques were detected, # list all uniques in the UI and let the user change names # force him to change names for taggedUniques with same AssetPath of course # if taggedDiscrepancyDetected: #for unique in taggedUniqueDict.keys(): # it is up to the UI to do that and let the user # set a new assetPath on any of those unique guy's lists #5. export files stuff for obj in taggedUniqueDict.keys(): exportObjectAsAsset(obj.name(), obj.attr("AssetPath").get()) #6. tell the editor to import all the uniques fileList = [] for obj in taggedUniqueDict.keys(): fileList.append(obj.attr("AssetPath").get()) m2u.core.getEditor().importAssetsBatch(fileList) #7. tell the editor to assemble the scene objInfoList = [] for obj in selectedMeshes: # TODO: make that a new function objInfo = ObjectInfo(name = obj.shortName(), typeInternal = "mesh", typeCommon = "mesh") objTransforms = m2u.maya.mayaObjectTracker.getTransformationFromObj(obj) objInfo.pos = objTransforms[0] objInfo.rot = objTransforms[1] objInfo.scale = objTransforms[2] objInfo.AssetPath = obj.attr("AssetPath").get() objInfoList.append(objInfo) m2u.core.getEditor().addActorBatch(objInfoList)