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

    Force VertexMap display

    Cinema 4D SDK
    python
    3
    18
    3.0k
    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.
    • R
      rui_mac
      last edited by

      I was creating the hidden VertexMap in the Init method of my plugin tag.
      But my main problem, like I described, was that activating the hidden VertexMap tag would show the VertexMap parameters in the Attribute Manager, instead of the parameters of my plugin tag.

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

        Hey @rui_mac,

        But my main problem, like I described, was that activating the hidden VertexMap tag would show the VertexMap parameters in the Attribute Manager

        I assume you mean here that the tag node was made the "active object" of the Attribute Manager, i.e., it had the same effect as if you called c4d.gui.ActiveObjectManager_SetObject on the hidden tag? This could be a side-effect of 'misbehaving' in a threaded environment, I assume you are in TagData::Execute, but it seems unlikely. Could you please share an executable example of your code, because otherwise it will be very hard for me to help you.

        Cheers,
        Ferdinand

        MAXON SDK Specialist
        developers.maxon.net

        R 1 Reply Last reply Reply Quote 0
        • R
          rui_mac @ferdinand
          last edited by

          @ferdinand, here is a version of my code with all the stuff that is not important for this problem, removed. Although, I fully commented everything I removed.
          I hope it is enough to spot the problem.

          import c4d
          import os
          import sys
          import math
          import random
          
          from c4d import gui,plugins,bitmaps,documents,utils,Matrix,BaseContainer
          from c4d import threading
          
          PLUGIN_ID = 1037312
          CHECK_ID = 1037275
          PLUGIN_NAME ="VertexMapMix"
          PLUGIN_VERSION = "1.1"
          url_access	= "vertexmapmix"
          
          # define the IDs of the interface elements
          VMM_ONOFF = 1000
          VMM_VERTEXMAP = 1001
          VMM_ATOMS = 1002
          VMM_CREATEVERTEXTAG = 1003
          VMM_SELECTVERTEXTAG = 1004
          
          VMM_ATOM_ONOFF = 2001
          VMM_ATOM_MIX = 2002
          VMM_ATOM_INVERT = 2003
          VMM_ATOM_SELECT = 2004
          VMM_ATOM_NAME = 2005
          VMM_ATOM_OPACITY = 2006
          VMM_ATOM_BLUR = 2007
          
          VMM_REMAPPING = 2020
          VMM_ATOM_CURVE = 2021
          
          VMM_SHOW = 2025
          
          VMM_MIX_NORMAL = 0
          VMM_MIX_MULT = 1
          VMM_MIX_MIN = 2
          VMM_MIX_MAX = 3
          VMM_MIX_ADD = 4
          VMM_MIX_SUB = 5
          
          icon_edit = c4d.bitmaps.BaseBitmap()
          
          # *******************************************************************************
          
          class vertex_mm(plugins.TagData):
          
          	old_selection	= 0
          	old_pressed		= -1
          	old_show 		= 1
          
          
          	def Init(self,node):
          
          		# in here I initialize all the GUI elements
          		# ...
          
          		# and in here I create the hidden VertexMap tag
          
          		op = node.GetMain()
              	if not isinstance(op, c4d.PolygonObject): return True
          
          			tags = op.GetTags()
          			hidden_tag = None
          			for tag in tags:
          				if tag[c4d.ID_BASELIST_NAME] == "vmm_hidden":
          					hidden_tag = tag
          					break
          
          			if hidden_tag == None:
          				pnum=op.GetPointCount()
          				hidden_tag=c4d.VariableTag(c4d.Tvertexmap,pnum)
          				op.InsertTag(hidden_tag)
          				hidden_tag[c4d.ID_BASELIST_NAME]="vmm_hidden"
          				hidden_tag.ChangeNBit(c4d.NBIT_OHIDE,True)
          		
          		return True
          
          # *******************************************************************************
          
          	def Message(self,node,type,data):
          
          		if node==None: return True
          
          		# this code would turn the display of the VertexMap values on or off,
          		# depending on the state of a checkbok
          
          		if type==c4d.MSG_DESCRIPTION_VALIDATE:
          			state =  node[VMM_SHOW]
          			if state != self.old_show:
          				self.old_show = state
          
          				op = node.GetObject()
          				if op.GetType() != 5100: return True
          
          				tags = op.GetTags()
          				hidden_tag = None
          				for tag in tags:
          					if tag[c4d.ID_BASELIST_NAME] == "vmm_hidden":
          						hidden_tag = tag
          						break
          
          				if hidden_tag == None:
          					pnum=op.GetPointCount()
          					hidden_tag=c4d.VariableTag(c4d.Tvertexmap,pnum)
          					op.InsertTag(hidden_tag)
          					hidden_tag[c4d.ID_BASELIST_NAME]="vmm_hidden"
          
          				hidden_tag.ChangeNBit(c4d.NBIT_OHIDE,True)
          				if state == 1:
          					hidden_tag.SetBit(c4d.BIT_ACTIVE)
          					op.SetBit(c4d.BIT_ACTIVE)
          					op.Message(c4d.MSG_UPDATE)
          				else:
          					hidden_tag.DelBit(c4d.BIT_ACTIVE)
          
          		return True
          
          # *******************************************************************************
          
          	def Execute(self,tag,doc,op,bt,priority,flags):
          
          		# here I do all the stuff that the plugin is supposed to do
          		# at the end, in the list called w_array1, are all the values
          		# to store in the hidden VertexMap tag, for display
          
          		tags = op.GetTags()
          		hidden_tag = None
          		for tag in tags:
          			if tag[c4d.ID_BASELIST_NAME] == "vmm_hidden":
          				hidden_tag = tag
          				break
          
          		if hidden_tag != None:
          			hidden_tag.SetAllHighlevelData(w_array1)
          
          		vertex_mix.SetAllHighlevelData(w_array1)
          		# the "vertex_mix" is a visible VertexMap tag, where the values are also stored.
          		# IMPORTANT!!!
          		# if it would be possible to make the VertexMap shading appear, simply by using
          		# this visible VertexMap tag, I would be more than fine with this.
          		# The problem, so far, is that I have to click this visible tag to make the
          		# shading appear, and that makes all the parameters of my plugin Tag disapear
          		# from the Attribute Manager.
          
          		op.Message(c4d.MSG_UPDATE)
          
          		return c4d.EXECUTIONRESULT_OK
          
          # *******************************************************************************
          
          # registration of the tag
          if __name__=="__main__":
          
          # Register the Tag
          	icon = c4d.bitmaps.BaseBitmap()
          	dir, file = os.path.split(__file__)
          	icon.InitWith(os.path.join(dir, "res", "icon_vmm.tif"))
          	icon_edit.InitWith(os.path.join(dir, "res", "edit.tif"))
          	c4d.gui.RegisterIcon(1037349,icon_edit)
          
          	plugins.RegisterTagPlugin(id=PLUGIN_ID, str=PLUGIN_NAME, g=vertex_mm,
          						 description="vertexmapmix", icon=icon,
          						 info=c4d.TAG_EXPRESSION|c4d.TAG_VISIBLE|c4d.TAG_MULTIPLE)
          	print("["+PLUGIN_NAME+" Tag - v"+PLUGIN_VERSION+"] by Rui Batista: Loaded")
          
          ferdinandF 2 Replies Last reply Reply Quote 0
          • ferdinandF
            ferdinand @rui_mac
            last edited by

            Hello @rui_mac,

            I will have a look tomorrow; I was busy today.

            Cheers,
            Ferdinand

            MAXON SDK Specialist
            developers.maxon.net

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

              Hello @rui_mac,

              So, I had a look at your code. It was unfortunately not executable in this form, even when I filled in some gaps like adding the necessary resources or re-indenting stuff. So, I cannot say many concrete things here, and only look at the general form.

              I must also point out again, that this is a hack and not officially supported. If you chose to take this route instead of drawing the data yourself, possible side effects will be up to you to handle.

              • A general thing is that you seem to assume that Message, Init, and other methods to always run on the main thread. But there is no guarantee for that; when you want to carry out actions only allowed on the main thread, you must still check for being on the main thread. I however still think that adding a tag from a non-main thread was not your issue here.
              • This line in your Init is dangerous and might be the source for your problems, as you forcibly select the host object of your tag: op.SetBit(c4d.BIT_ACTIVE)
              • In general, you are also very generous with Message(c4d.MSG_UPDATE) calls. They are first of all here unnecessary for the flag changes and secondly also dangerous. I would never send messages to a node which is already part of the scene graph from threaded environment (you call op.Message(c4d.MSG_UPDATE) in multiple contexts).
              • Using the name of a node is a poor way of identifying a node, you should use UUIDs in Python.
              • I have provided below a TagData solution for your problem. This is quite a bit of busy work, especially when you want to properly clean up after yourself. This works fine for me.

              if it would be possible to make the VertexMap shading appear, simply by using this visible VertexMap tag, I would be more than fine with this.

              It would be the same, just iterate over the tags of the node hosting your tag and select whatever tags you want to select.

              Cheers,
              Ferdinand

              The result:
              9969441c-0ebc-4bca-a376-a693f5795e07-image.png
              The plugin: pc14184.zip

              The pyp file of the plugin:

              """Implements a tag which manages a hidden vertex map to draw data.
              """
              import c4d
              import typing
              
              class VertexMapMixData(c4d.plugins.TagData):
                  """Implements a tag which manages a hidden vertex map to draw data.
                  """
                  PLUGIN_ID: int = 1037312
                  DRAWTAG_ID: int = 1060040
              
                  PLUGIN_NAME: str = "VertexMapMix"
                  PLUGIN_DESCRIPTION: str = "Tvertexmapmix"
              
                  def __init__(self) -> None:
                      """Initializes the instance, specifically its _hiddenDrawTags lookup table.
                      """
                      self._hiddenDrawTags: dict[bytes: c4d.BaseTag] = {}
                      super().__init__()
              
                  def FlushHiddenTags(self, node: c4d.GeListNode) -> None:
                      """Flushes the #_hiddenDrawTags cache and removes all the hidden tags from the hosting node.
                      """
                      host: typing.Optional[c4d.BaseObject] = node.GetMain()
                      if not isinstance(host, c4d.BaseObject):
                          return
              
                      tag: c4d.BaseTag
                      for tag in host.GetTags():
                          if tag.GetType() != c4d.Tvertexmap:
                              continue
              
                          # This is a hidden drawing tag instance.
                          uuid: typing.Optional[memoryview] = tag.FindUniqueID(VertexMapMixData.DRAWTAG_ID)
                          if isinstance(uuid, memoryview):
                              tag.Remove()
              
                      self._hiddenDrawTags = {}
              
                  def GetHiddenDrawTag(self, node: c4d.PolygonObject) -> typing.Optional[c4d.VariableTag]:
                      """Retrieves the hidden drawing tag on a polygon object.
                      """
                      # Bail when #node is not a polygon object or not attached to a document.
                      if (not isinstance(node, c4d.PolygonObject) or
                              not isinstance(node.GetDocument(), c4d.documents.BaseDocument)):
                          return None
              
                      # Get the UUID of the polygon object so that we look up its hidden tag in the lookup.
                      uuid: typing.Optional[memoryview] = node.FindUniqueID(c4d.MAXON_CREATOR_ID)
                      if not isinstance(uuid, memoryview):
                          return None
              
                      # Check if there is already a cached hidden draw tag in dictionary of the plugin interface.
                      hiddenTag: typing.Optional[c4d.BaseTag] = self._hiddenDrawTags.get(bytes(uuid), None)
                      if isinstance(hiddenTag, c4d.BaseTag) and hiddenTag.IsAlive():
                          return hiddenTag
              
                      # Just for verbosity here to make clear that this can still return None.
                      return None
              
                  def SetHiddenDrawTag(self, node: c4d.BaseTag) -> None:
                      """Creates the hidden drawing tag on a polygon object.
                      """
                      # The host is not a polygon object or there is already a valid drawing tag on that host.
                      if (not isinstance(node, c4d.PolygonObject) or
                              isinstance(self.GetHiddenDrawTag(node), c4d.VariableTag)):
                          return
              
                      # Flush the cache to remove any lingering tags and get the UUID of the polygon object so
                      # that we cache the hidden draw tags.
                      self.FlushHiddenTags(node)
                      hostUuid: typing.Optional[memoryview] = node.FindUniqueID(c4d.MAXON_CREATOR_ID)
                      if not isinstance(hostUuid, memoryview):
                          return
              
                      # Start creating the new tag, for that we must be on the main thread.
                      if not c4d.threading.GeIsMainThread():
                          return
              
                      tag: c4d.VariableTag = c4d.VariableTag(c4d.Tvertexmap, node.GetPointCount())
                      if not isinstance(tag, c4d.BaseTag):
                          raise MemoryError("Could not allocate vertex map tag.")
              
                      tag.AddUniqueID(VertexMapMixData.DRAWTAG_ID, bytes(True))
                      tag.ChangeNBit(c4d.NBIT_OHIDE, True)
                      node.InsertTag(tag)
              
                      # Put the tag in the lookup table.
                      self._hiddenDrawTags[bytes(hostUuid)] = tag
              
                  def Init(self, node: c4d.GeListNode) -> bool:
                      """Called by Cinema 4D to initialize the node.
                      """
                      self.InitAttr(node, c4d.BaseList2D, c4d.ID_SOURCE_DATA)
                      self.InitAttr(node, bool, c4d.ID_DRAW_DATA)
              
                      node[c4d.ID_SOURCE_DATA] = None
                      node[c4d.ID_DRAW_DATA] = True
              
                      # Attempt to create the tag, as all methods, Init is not guaranteed to be on the main thread.
                      self.SetHiddenDrawTag(node.GetMain())
                      return True
              
                  def Free(self, node: c4d.GeListNode) -> None:
                      """Called by Cinema 4D when the node is about to be destroyed.
              
                      Will remove all hidden tags on the node.
                      """
                      self.FlushHiddenTags(node)
              
                  def CopyTo(self, dest: c4d.plugins.NodeData, snode: c4d.GeListNode, dnode: c4d.GeListNode,
                             flags: int, trn: c4d.AliasTrans) -> bool:
                      """Called by Cinema 4D when a tag is being copied.
              
                      Will remove all hidden tags on the source node, so that when a user drag-and_drops the tag 
                      from object A to object B, object A is being cleaned up.
                      """
                      self.FlushHiddenTags(snode)
                      return True
              
                  def Message(self, node: c4d.GeListNode, type: int, data: object) -> bool:
                      """Called by Cinema 4D to send messages to the node.
              
                      We just use this here as something that is called often on the main thread. But there is
                      no guarantee that this will run on the main thread, you were sending yourself a message, 
                      MSG_UPDATE, to a node outside of the main thread in your TagData::Execute. Never assume
                      Message to run on the main thread only.
                      """
                      # Try to create the tag, could be throttled by picking a specific message ID which is
                      # called often enough.
                      self.SetHiddenDrawTag(node.GetMain())
                      return True
              
                  def Execute(self, tag: c4d.BaseTag, doc: c4d.documents.BaseDocument, op: c4d.BaseObject,
                              bt: c4d.threading.BaseThread, priority: int, flags: int) -> int:
                      """Called by Cinema 4D to execute the tag.
                      """
                      state: bool = tag[c4d.ID_DRAW_DATA]
                      src: typing.Optional[c4d.VariableTag] = tag[c4d.ID_SOURCE_DATA]
                      dst: typing.Optional[c4d.VariableTag] = self.GetHiddenDrawTag(op)
              
                      # There is no source or destination tag we could write from or to.
                      if not isinstance(src, c4d.VariableTag) or not isinstance(dst, c4d.VariableTag):
                          return c4d.EXECUTIONRESULT_OK
              
                      # Select or deselect the hidden tag.
                      if tag[c4d.ID_DRAW_DATA]:
                          dst.SetBit(c4d.BIT_ACTIVE)
                      else:
                          dst.DelBit(c4d.BIT_ACTIVE)
              
                      # Determine of we should copy the data and then copy the data.
                      if (src.GetType() != dst.GetType() or
                              src.GetDataCount() != dst.GetDataCount()):
                          return c4d.EXECUTIONRESULT_OK
                      dst.SetAllHighlevelData(src.GetAllHighlevelData())
              
                      return c4d.EXECUTIONRESULT_OK
              
              
              def RegisterPlugin() -> None:
                  """Registers the plugin.
                  """
                  if not c4d.plugins.RegisterTagPlugin(
                          id=VertexMapMixData.PLUGIN_ID,
                          str=VertexMapMixData.PLUGIN_NAME,
                          g=VertexMapMixData,
                          description=VertexMapMixData.PLUGIN_DESCRIPTION,
                          icon=c4d.bitmaps.InitResourceBitmap(c4d.FLgradient),
                          info=c4d.TAG_EXPRESSION | c4d.TAG_VISIBLE | c4d.TAG_MULTIPLE):
                      raise RuntimeError(f"Failed to register: {VertexMapMixData}")
              
              
              if __name__ == "__main__":
                  RegisterPlugin()
              
              

              MAXON SDK Specialist
              developers.maxon.net

              R 1 Reply Last reply Reply Quote 0
              • R
                rui_mac @ferdinand
                last edited by

                @ferdinand, thank you for the excellent advices.
                I hope I can get home soon enough after work to clean up my code according to your advices and implement your solution.
                Also, your advices will allow me to clean up some of my other plugins.
                Thank you very much, once again.

                1 Reply Last reply Reply Quote 0
                • R
                  rui_mac
                  last edited by

                  Well, it kind of works. When I adjust parameters in the plugin tag, sometimes the Attribute Manager reverts to show "2 Elements [VertexMap,ThePluginName]"
                  I even tried with your plugin and, by twirling the Data Source parameter open and, for example, turning the Invert option on, the Attribute Manager stops showing the plugin parameters, and starts showing "2 Elements [VertexMap,VertexMapMax]"

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

                    Hello @rui_mac,

                    ah, okay, I understand what you mean. Yeah, that is a bit odd and also makes this solution not very viable. As I said before, this always was a hack. When I did this myself a few years ago, I think I never ran into this issue.

                    The underlying problem here is that the Attribute Manager is able to display a hidden tag as selected and the whole setup relies on the fact that hidden tags are being ignored in that context. It is also very erratic how this happens, when I for example click long enough on the Draw Data bool, the problem will also occur at some point and at the same time, it sometimes it does not happen 'inside' the vertexmap when I change a parameter there.

                    Since this seems to be tied to parameter writing events, you could try to switch off the hidden tag being selected before a parameter is being written and turn it back on after that. But that will become really complicated or even possible to do for 'buried' parameters changes, such as for example, changing something in a field driving a vertex map which is linked in the tag.

                    I will file this as a bug because the Attribute Manager should not be able to display hidden tags in single or multi-selections. In the meantime, I can only recommend using C++ and drawing things yourself or using CustomDataTagDisplayInterface, as this will be performant and is also guaranteed to work.

                    Cheers,
                    Ferdinand

                    MAXON SDK Specialist
                    developers.maxon.net

                    R 1 Reply Last reply Reply Quote 0
                    • R
                      rui_mac @ferdinand
                      last edited by

                      @ferdinand, thank you for the answer and explanation. I will try to find a compromise.
                      In the meanwhile, one can always select the tag, lock the Attribute Manager and click the VertexMap tag to display the result. Cumbersome, but it works.

                      1 Reply Last reply Reply Quote 0
                      • ymoonY
                        ymoon
                        last edited by

                        @ferdinand, Is it possible to VertexColor, WeightMap(character rig) in the same way?
                        Some games use VertexColor, so marking can always be useful.
                        I tried to convert your code to VertexColor, but I lack knowledge. Please help us.

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