GetActiveUVSet() returns None if multiple UVs are active?
-
Is there a way to get the component selections in the Texture Editor If I have multiple UVs active? (multiple tags/objects selected in OM)
Normally I'd use GetActiveUVSet() which returns a TempUVHandle. But if there's more than one UV/Object active it returns None.
I tried looking for anything in the C++ and Python SDK, but I couldn't find anything. The documentation says TempUVHandle came out before the multi-UV editing was introduced in C4D. Is there an undocumented method of handling multiple UVs, or is it not exposed in the python/c++ API?
-
Hello @Gene,
Thank you for reaching out to us. I see that you have been registered for a long time, but since this is your first posting: Welcome to the Plugin Café forum and the Cinema 4D development community, it is great to have you with us!
Getting Started
Before creating your next postings, we would recommend making yourself accustomed with our Forum and Support Guidelines, as they line out details about the Maxon SDK Group support procedures. Of special importance are:
- Support Procedures: Scope of Support: Lines out the things we will do and what we will not do.
- Support Procedures: Confidential Data: Most questions should be accompanied by code but code cannot always be shared publicly. This section explains how to share code confidentially with Maxon.
- Forum Structure and Features: Lines out how the forum works.
- Structure of a Question: Lines out how to ask a good technical question. It is not mandatory to follow this exactly, but you follow the idea of keeping things short and mentioning your primary question in a clear manner.
About your First Question
As far as I can see,
c4d.modules.bodypaint.GetActiveUVSet
requires a selected object, and other than for the application behavior, a selected UVW tag alone is not enough. This is independent from multi UVW tags being present or not. But when there is an object selection, and the object has multiple tags, a tag selection is respected correctly byGetActiveUVSet
.Unfortunately, your question is also a bit ambiguous, as you do not explain what you would consider valid UVW data in a '(multiple tags/objects selected in OM)'. A
c4d.modules.bodypaint.TempUVHandle
represents the data of a singular UVW map, so if you want to access or modify the data of multiple tags you must iterate over them yourself.Cheers,
FerdinandResult:
Code:
"""Demonstrates using the BodyPaint UVW handler TempUVHandle with objects with multiple UVW tags. """ import c4d from c4d.modules.bodypaint import TempUVHandle, GetActiveUVSet, UpdateMeshUV doc: c4d.documents.BaseDocument def main(): """ """ # Get the selected objects and tags in the document. objSelection: list[c4d.BaseObject] = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_CHILDREN) tagSelection: list[c4d.BaseTag] = doc.GetActiveTags() # Build the set of all selected objects and the objects of the selected UVW tags. So, # #joinedSelection now contains either objects with no UVW tag selected, or objects which are # not selected but have at least one UVW tag which is selected. joinedSelection: list[c4d.BaseObject] = objSelection + [ tag.GetObject() for tag in tagSelection if tag.CheckType(c4d.Tuvw) and tag.GetObject() not in objSelection ] for obj in joinedSelection: # For each object in this join, get all UVW tags on it and check which ones are selected. # When None are selected, assume that we want all UVW data on that object, if there is a # selection, only pick the selected UVW tags. uvwTags: list[c4d.BaseTag] = [tag for tag in obj.GetTags() if tag.CheckType(c4d.Tuvw)] selection: list[int] = [tag for tag in uvwTags if tag in tagSelection] or uvwTags print (f"{obj.GetName() = }") for tag in selection: # c4d.modules.bodypaint.GetActiveUVSet does not seem to work without a selected # object, multi UVW or not. But it does correctly respect selected tags. doc.SetActiveObject(tag.GetObject()) doc.SetActiveTag(tag) # Fully update the BodyPaint data and push an update event to CInema 4D. UpdateMeshUV(True) c4d.EventAdd() # Get the UVW handler for the current document state. handle: TempUVHandle | None = GetActiveUVSet(doc, c4d.GETACTIVEUVSET_ALL) if not handle: raise RuntimeError("Could not access UVW handler.") # And do something with it ... print (f"\t{tag.GetName() = }") for i, poly in enumerate(handle.GetUVW()): print (f"\t\t{i}: {poly}") # Restore the selection state of the document from when the script has been invoked. if objSelection: doc.SetActiveObject(objSelection[0], c4d.SELECTION_NEW) for obj in objSelection[1:]: doc.SetActiveObject(obj, c4d.SELECTION_ADD) if tagSelection: doc.SetActiveTag(tagSelection[0], c4d.SELECTION_NEW) for tag in tagSelection[1:]: doc.SetActiveTag(tag, c4d.SELECTION_ADD) c4d.EventAdd() if __name__ == "__main__": main()
-
@ferdinand said in GetActiveUVSet() returns None if multiple UVs are active?:
Unfortunately, your question is also a bit ambiguous, as you do not explain what you would consider valid UVW data in a '(multiple tags/objects selected in OM)'. A c4d.modules.bodypaint.TempUVHandle represents the data of a singular UVW map, so if you want to access or modify the data of multiple tags you must iterate over them yourself.
Aye, that's the info I needed. I wanted to process only the UVs that show up in the Texture/UV Editor. So if the Object Manager has more than one UV tag selected on a single object, or if a Material tag is selected, the code would ignore the selected tags that aren't active.
For example in the screenshot below, CubeA has two UV tags selected, and CubeB has its Material tag selected. Only CubeA's first UV tag and CubeB's second UV tag show up in the Texture/UV Editor, so only those two tags would be processed.
I adjusted the code you gave me to only work on the Material/UV tags that show up in the Texture/UV Editor. (I might modify it later to factor in the Material tag's Offset/Length/Tile values)
So far it's doing exactly what I need. Only issue I have is if I need to undo the script. I have to press the Undo button several times because C4D creates an undo state for every Tag/Object selection step in the script, instead of putting all of them under one undo state. I think this is a long-existing limitation of C4D's Undo system?
"""Name-en-US:snap uv to left Description-en-US:sets selected UV points to left edge of UV tile """ import c4d from c4d.modules.bodypaint import GetActiveUVSet, UpdateMeshUV, TempUVHandle #============================================================================== # Only one UV tag can be active per object. # If an object has multiple UV/Material tags, only one UV is displayed in the Texture Editor no matter how many tags are selected. # If multiple UV/Material tags are selected on one object, the leftmost tag is used def GetActiveUVTags(): # Collect selected uvw and material tags selected_tags = [tag for tag in doc.GetActiveTags() if tag.CheckType(c4d.Tuvw) or tag.CheckType(c4d.Ttexture)] active_objects:list[c4d.BaseObject] = [] active_uv_tags:list[c4d.BaseTag] = [] # Only pick first material/uv tag in an object, ignore the rest for selected_tag in selected_tags: if selected_tag.GetObject() in active_objects: #If active tag of an object has been found, skip continue if selected_tag.CheckType(c4d.Tuvw): # First selected UV tag on the object, skip the rest active_objects.append(selected_tag.GetObject()) active_uv_tags.append(selected_tag) elif selected_tag.CheckType(c4d.Ttexture): # First selected Material tag on the object, look for the UV it uses. # If a Material tag is selected, the active UV is the first UV tag to the right of the material tag. # If there is no UV tag to the right of the Material tag, the leftmost UV tag is active. active_uv_tag:c4d.BaseTag = None tag = selected_tag.GetNext() while tag: # Look for UV tag to the right of the Material tag if tag.CheckType(c4d.Tuvw): active_uv_tag = tag break tag = tag.GetNext() if active_uv_tag is None: # If there's no tag to the right of the Material tag, look for first UV tag on object active_uv_tag = selected_tag.GetObject().GetTag(c4d.Tuvw) if active_uv_tag: active_objects.append(selected_tag.GetObject()) active_uv_tags.append(active_uv_tag) # Get selected objects that have no selected tags selected_objects = [object for object in doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_CHILDREN) if object not in active_objects] for selected_object in selected_objects: # Get first UV tag of object tag = selected_object.GetTag(c4d.Tuvw) if tag: active_uv_tags.append(tag) print(selected_objects) print(active_uv_tags) return active_uv_tags def SetPoints( sel, uv ): for i, selected in enumerate(sel): if not selected: continue uv[i//4][list(uv[i//4].keys())[i%4]][0] = 0.0 return uv def RestoreOldSelection(old_objects, old_tags): if old_objects: doc.SetActiveObject(old_objects[0], c4d.SELECTION_NEW) for obj in old_objects[1:]: doc.SetActiveObject(obj, c4d.SELECTION_ADD) if old_tags: doc.SetActiveTag(old_tags[0], c4d.SELECTION_NEW) for obj in old_tags[1:]: doc.SetActiveTag(obj, c4d.SELECTION_ADD) return #============================================================================== def main(): old_objects = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_CHILDREN) old_tags = doc.GetActiveTags() active_uv_tags = GetActiveUVTags() # Deselect objects, only have one tag active at a time doc.StartUndo() for tag in active_uv_tags: doc.AddUndo(c4d.UNDOTYPE_BITS, tag.GetObject()) doc.AddUndo(c4d.UNDOTYPE_BITS, tag) doc.SetActiveObject(tag.GetObject()) doc.SetActiveTag(tag) UpdateMeshUV(True) handle: TempUVHandle | None = GetActiveUVSet(doc, c4d.GETACTIVEUVSET_ALL) if not handle: raise RuntimeError("Could not access UVW handler.") handle = GetActiveUVSet(doc, c4d.GETACTIVEUVSET_ALL) uv_sel = handle.GetUVPointSel() uv = handle.GetUVW() sel = uv_sel.GetAll(handle.GetPolyCount()*4) handle.SetUVW( SetPoints( sel, uv ) ) # restore previous object/tag selection before UV processing RestoreOldSelection(old_objects, old_tags) doc.EndUndo() c4d.EventAdd() if __name__=='__main__': main()
-
Hello @Gene,
So far it's doing exactly what I need. Only issue I have is if I need to undo the script. I have to press the Undo button several times because C4D creates an undo state for every Tag/Object selection step in the script, instead of putting all of them under one undo state. I think this is a long-existing limitation of C4D's Undo system?
Well, it depends a bit on what you would consider a "long-existing limitation", but you can consolidate multiple operations into a singular item in the undo-stack. The most relevant methods are BaseDocument.StartUndo, BaseDocument.EndUndo, and BaseDocument.AddUndo (there more undo related methods on
BaseDocument
).Cheers,
Ferdinand"""Demonstrates wrapping selecting all top-level objects in a document into one undo item. """ import c4d doc: c4d.documents.BaseDocument def main(): """ """ obj: c4d.BaseObject = doc.GetFirstObject() # Start an undo item in the undo stack. doc.StartUndo() while obj: # Add an operation to the undo item, most operations must be invoked BEFORE the actual # operation is carried out. The only exception is inserting new nodes, AddUndo must here # be called AFTER the operation. doc.AddUndo(c4d.UNDOTYPE_ACTIVATE, obj) doc.SetActiveObject( obj, c4d.SELECTION_NEW if obj == doc.GetFirstObject() else c4d.SELECTION_ADD) obj = obj.GetNext() # Close the item. doc.EndUndo() c4d.EventAdd() if __name__ == "__main__": main()