Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    GetActiveUVSet() returns None if multiple UVs are active?

    Cinema 4D SDK
    python
    2
    4
    800
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • G
      Gene
      last edited by

      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?

      ferdinandF 1 Reply Last reply Reply Quote 0
      • ferdinandF
        ferdinand @Gene
        last edited by ferdinand

        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 by GetActiveUVSet.

        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,
        Ferdinand

        Result:
        28aff33f-393e-4e10-809a-d26c88110dc5-image.png

        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()
        

        MAXON SDK Specialist
        developers.maxon.net

        G 1 Reply Last reply Reply Quote 1
        • G
          Gene @ferdinand
          last edited by Gene

          @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.
          8aa54c50-459a-456d-9e69-8ebe857b934b-image.png

          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()
          
          ferdinandF 1 Reply Last reply Reply Quote 0
          • ferdinandF
            ferdinand @Gene
            last edited by

            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()
            

            MAXON SDK Specialist
            developers.maxon.net

            1 Reply Last reply Reply Quote 0
            • First post
              Last post