def test_topologyMatch(src, tgt): '''Tests if the topologies match between two mesh transforms Args: src (pm.nt.Transform): the source object tgt (pm.nt.Transform): the target object Returns (boolean): True if success, warning and False if not Usage: test_topologyMatch( pm.ls(sl=True)[0], pm.ls(sl=True)[1] ) ''' inconsistencies = { 128:'User Normals', 64:'Color Indices', 32:'Color Sets', 16:'UV Indices', 8:'UV sets', 4:'Face Desriptions', 2:'Edges', 1:'Verts' } compare_value = pm.polyCompare([src, tgt]) for key in reversed( sorted( inconsistencies.keys() ) ): if (compare_value-key) < 0: print "popping "+str(key) inconsistencies.pop(key, None) else: compare_value-=key mismatches = [inconsistencies[key] for key in sorted(inconsistencies.keys())] if mismatches: pm.warning ('Sorry, your geometry %s does not match %s,' 'here are the things that don\'t match:' '\n\t%s'%(src.name(), tgt.name(), "\n\t".join(mismatches))) return False return True
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 _find_uniques(self, selected_meshes): """Assemble the `asset_list` and detect `tagged_discrepancy` by checking the meshes for their `AssetPath` attribute and geometric parity. Uniques here means geometric objects that have the same geometry even if they are not instantiated. When normally duplicating objects in Maya, the copy will be a different geometry object, but both, original and copy, will come down to the same 'unique' mesh by comparing their geometry. """ # For each object check the "AssetPath" attribute. Those which # have one are considered 'tagged'. untagged_list = list() tagged_dict = {} # Map AssetPath strings to all objects using it for obj in selected_meshes: if obj.hasAttr("AssetPath"): asset_path_attr = obj.attr("AssetPath") asset_path_value = asset_path_attr.get() if len(asset_path_value) > 0: tagged_dict.setdefault(asset_path_value, []).append(obj) else: # If the asset path is empty, that is equal to the # attribute not existing on the object. untagged_list.append(obj) else: # Unknown asset, we will handle those later. untagged_list.append(obj) _lg.debug("Found %i untagged objects." % len(untagged_list)) _lg.debug("Found %i tagged objects." % len(tagged_dict)) # Do the geometry check for 'tagged' objects: # Assemble the `tagged_unique_dict`, which maps an object to a # list of all other objects that have the same geometry. # A 'tagged discrepancy' is when objects sharing the same # geometry have different "AssetPath" values. tagged_discrepancy_detected = False tagged_unique_dict = {} for object_list in tagged_dict.values(): while len(object_list) > 0: obj = object_list[0] tagged_unique_dict[obj] = [] # Compare this object against all others in the list. for other_obj in object_list[1:]: have_same_geometry = (pm.polyCompare(obj, other_obj, vertices=True) == 0) if have_same_geometry: # If the geometry matches, add the other to the # unique list with this object as key, and remove # from the old list. tagged_unique_dict[obj].append(other_obj) object_list.remove(other_obj) else: # TODO: Create new unique for objects that have # same AssetPath but different geometry. tagged_discrepancy_detected = True object_list.remove(obj) _lg.debug("Found %i tagged uniques." % len(tagged_unique_dict)) # Do the geometry check for 'untagged' objects: # If tagged objects with the same geometry exist, add the object # to the respective list and use their AssetPath. # If there is no unique yet for the geometry, use the first # object with that geometry as a new unique, and use its name as # AssetPath. untagged_uniques_detected = False while len(untagged_list) > 0: obj = untagged_list[0] if not obj.hasAttr("AssetPath"): pm.addAttr(obj.name(), longName="AssetPath", dataType="string", keyable=False) found_unique_for_me = False # Compare against one of the tagged uniques. for unique_obj in tagged_unique_dict.keys(): have_same_geometry = (pm.polyCompare(obj, unique_obj, vertices=True) == 0) if have_same_geometry: # If that geometry matches, we found the unique to # use for this obj. tagged_unique_dict[unique_obj].append(obj) asset_path = unique_obj.attr("AssetPath").get() obj.attr("AssetPath").set(asset_path) found_unique_for_me = True untagged_list.remove(obj) _lg.debug( "Found an existing unique key ({0}) for {1}".format( unique_obj.name(), obj.name())) break if not found_unique_for_me: untagged_uniques_detected = True # Make this a new unique. Simply take the object's name # as AssetPath, but remove any trailing numbers for # convenience. npath = obj.shortName() npath = helper.remove_number_suffix(npath) obj.attr("AssetPath").set(npath) tagged_unique_dict[obj] = [] untagged_list.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 {0} uniques (including untagged).".format( len(tagged_unique_dict))) # Create a string-based asset list for the UI and further # operations. This is not a dict because there may be tagged # discrepancies (dupl keys). asset_list = [] for unique_obj, instance_objects in tagged_unique_dict.items(): path = unique_obj.attr("AssetPath").get() # New entry for that AssetPath with first object. entry = AssetListEntry(path) entry.append(unique_obj.shortName(), unique_obj) # Append all other objects that match the geo. for instance in instance_objects: entry.append(instance.shortName(), instance) asset_list.append(entry) self.asset_list = asset_list self.untagged_uniques_detected = untagged_uniques_detected self.tagged_discrepancy_detected = tagged_discrepancy_detected
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)