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

    [Solved] How to setup a CTrack on TAG plugin UI slider? [with extended details]

    Cinema 4D SDK
    python
    2
    9
    6.9k
    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.
    • mocolocoM
      mocoloco
      last edited by mocoloco

      Hello there,

      I'm currently working on a plugin development in Python under R26 and I'm struggling to reach a resource ID on the GUI to assign it an animation track.
      I tried to implement what I was using for the objects hoping it will worked, but it isn't - I'm getting error 5350 doing so.

      Reading the SDK, it seems, as far as I understand, that BaseTag inherit of BaseList2D and should so inherit of CTrack; but there should be something wrong here in the code or SDK which I do not understand. As the plugin is declared as a plugins.RegisterTagPlugin() shouldn't it
      be inherit of BaseList2D?

      Here below the excerpt of the code for a better understanding,

      mAXIS_X = 1550
      mAXIS_Y = 1551
      mAXIS_Z = 1552
      
      def Init(self, node):
              mmObj = c4d.documents.GetActiveDocument().GetActiveTag()
              if mmObj is not None:
                  print ( f"mmObj: { mmObj }" )
                  print ( f"Object: { mmObj.GetObject() }" )
                  print ( f"tagName : {mmObj} / {mmObj.GetObject().GetName()} plugin ID: { mPLUGIN_ID }" )
                  descID = c4d.DescID( c4d.DescLevel(c4d.ID_BASEOBJECT_ROTATION, c4d.DTYPE_VECTOR, 0), c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, 0) )
                  c4d.CTrack(mmObj, descID )
      

      gives the following console log and error,

      mmObj: <c4d.BaseTag object called mMover/mMover with ID 1059303 at 11778301312>
      Object: <c4d.BaseObject object called mMover/Null with ID 5140 at 11778302720>
      tagName : <c4d.BaseTag object called mMover/mMover with ID 1059303 at 11778301312> / mMover plugin ID: 1059303
      Traceback (most recent call last):
        File "~/Library/C4D_Plugins/C4D_mLab_Plugins_R23/mMover.pyp", line 266, in Init
          c4d.CTrack(mmObj, descID )
      BaseException: the plugin 'c4d.CTrack' (ID 5350) is missing. Could not allocate instance
      

      The pict below shows the UI on which 3 sliders exists (mAXIS_XX, ID from 1550 to 1552). That those sliders on which I would like to create a CTrack. Once done, the button "Clear all animation tracks" will remove them.

      Screenshot 2022-12-16 at 16.43.40.png

      I setup a .res resource file with this,

                 GROUP {         
                      DEFAULT 1;
                      COLUMNS 2;
      
                      REAL mAXIS_X           { MIN -180.0; MAX 180.0; UNIT DEGREE; STEP 0.05; CUSTOMGUI REALSLIDER; SCALE_H; }
                      BOOL mEN_X             { ANIM OFF; }
                      STATICTEXT              { JOINENDSCALE; }
      
                      REAL mAXIS_Y           { MIN -180.0; MAX 180.0; UNIT DEGREE; STEP 0.05; CUSTOMGUI REALSLIDER; SCALE_H; }
                      BOOL mEN_Y             { ANIM OFF; }
                      STATICTEXT              { JOINENDSCALE; }
      
                      REAL mAXIS_Z           { MIN -180.0; MAX 180.0; UNIT DEGREE; STEP 0.05; CUSTOMGUI REALSLIDER; SCALE_H; }
                      BOOL mEN_Z             { ANIM OFF; }
                      STATICTEXT              { JOINENDSCALE; }
      
                  }
      

      The sliders have the following IDs no .h file,

       mAXIS_X = 1550,
       mAXIS_Y,
       mAXIS_Z,
      

      I tried many ways to reach the right ID (1550 here), but I never succeed; and I never got the Animation track created in any way for this slider.

      When using the Execute method of the plugin I can easily reach the sliders values, but again, I can't succeed to assign a CTrack on them,

      def Execute(self, tag, doc, op, bt, priority, flags): 
              print ( f"X Slider value: { c4d.utils.RadToDeg(tag[mAXIS_X]) }")
      descID = c4d.DescID( c4d.DescLevel(c4d.ID_BASEOBJECT_ROTATION, c4d.DTYPE_VECTOR, 0), c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, 0) )
              xTrack = c4d.CTrack(tag, descID )
              xTrack.GetCurve()
      

      returns the following,

      X Slider value:: 0.0
      mMover/Null with ID 5140 at 11746145920>
      Traceback (most recent call last):
        File "~/Library/C4D_Plugins/C4D_mLab_Plugins_R23/mMover/mMover.pyp", line 313, in Execute
          xTrck = c4d.CTrack(tag, descID )
      BaseException: the plugin 'c4d.CTrack' (ID 5350) is missing. Could not allocate instance
      

      If someone knows how to get rid of this problem it would help me a lot, as well as maybe the whole community.

      Thanks!
      Christophe

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

        Hey @mocoloco,

        Thank you for reaching out to us and thank you for your extensive documentation efforts.

        There is unfortunately no executable code which makes it always hard to understand and find problems, but what stands out to me is:

        • It is not clear to me at whose Init method we are looking at, but from the context I assume it is mMoverTagData.Init
        • mmObj = c4d.documents.GetActiveDocument().GetActiveTag() should not be used in a NodeData plugin. You should use node passed into the function when you want to reference the tag instance itself. When mmObj is not meant to be the tag itself, then you should get the node document with node.GetDocument() and search there for your tag.
        • mmObj is either a BaseTag or None.
        • You construct a DescID for (ID_BASEOBJECT_ROTATION, VECTOR_X) which expands to (904, 1000).
        • A BaseTag has no vector parameters where a BaseObject has its base type vector parameters as for example ID_BASEOBJECT_ROTATION, etc. (a tag has no transform).
        • The float parameters mAXIS_X, mAXIS_Y, and mAXIS_Z in your plugin start at 1550.
        • With c4d.CTrack(mmObj, descID):
          • You are trying to write into an non-existent parameter in mmObj (unless you defined a vector at 904 and did not show that).
          • It is a bit ambiguous what you want to do here, but I assume you want to create a track for your mAXIS_X parameter.
          • But your DescId is for a float component inside a vector and you define a float, not a vector.

        You seem to misunderstand how DescID work, which is not too surprising since they are sparsely documented. Find below an example or look at this posting for more information.

        # This DescID addresses the X component of a vector parameter called ID_M_MOVE_AXIS.
        descId: c4d.DescID= c4d.DescID(
            # The first desc level, we are addressing here the vector itself.
            c4d.DescLevel(   
                c4d.ID_M_MOVE_AXIS, # The ID of the parameter.
                c4d.DTYPE_VECTOR,   # Its data type.
                0),                 # The creator ID, 0 means undefined.
            # The second desc level, here we are addressing a sub-channel/parameter within a parameter, here
            # the X component of #ID_M_MOVE_AXIS.
            c4d.DescLevel(
                c4d.VECTOR_X,      # The ID
                c4d.DTYPE_REAL,    # The data type. 
                0)                 # The creator ID.
            )
        

        So, when you want to create a track for the mAXIS_X parameter on an instance of your tag plugin, its ID would be:

        xAxisId: c4d.DescID = c4d.DescID(c4d.DescLevel(c4d.mAXIS_X, c4d.DTYPE_REAL, 0))
        

        Cheers,
        Ferdinand

        MAXON SDK Specialist
        developers.maxon.net

        mocolocoM 1 Reply Last reply Reply Quote 0
        • mocolocoM
          mocoloco @ferdinand
          last edited by mocoloco

          Hi @ferdinand,

          Thanks a lot for the whole explanation. Indeed, it's a bit confuse on how to point some ID. I'm sorry to not share the whole source code but it's under NDA, so I can only share some excerpt, and that do not help to have the big picture, sorry for that.

          I went a bit in-depth and tried some stuff after reading your explanations, and the posting on DescID (even if I still don't get why I have to write c4d.mAXIS_X instead of mAXIS_X).

          Let me rephrase some part to be on the same page and to remove a the confusions.

          First, as I was stuck on getting what I tried on Execute(), which was the place where I would like to set the CTrack, I indeed tried to get it set in the mMoverTagData.Init() - which I agree, is maybe not the right way to do.

          Then, mAXIS_X is indeed a real and not a vector which is displayed in a slider form.

          So, I went back to my first idea to set the CTrack inside the Execute(self, tag, doc, op, bt, priority, flags) method and made the DescID from what I understood. To be clear I want to create a CTrack for the mAXIS_X parameter with ID 1550

          I wrote this,

          mPLUGIN_ID = 1059303 #Unique ID
          mTAG_NAME = "mMover"
          mAXIS_A1 = 1550
          
          class mMover ( plugins.TagData ):
          
              def __init__ (self):
                  super(mMover, self).__init__()
                  pass
          
          
              def Execute(self, tag, doc, op, bt, priority, flags): 
                  print ( f"GetTags {op.GetTag( mPLUGIN_ID )}" )
                  currentTAG = op.GetTag( mPLUGIN_ID )
                  
                  descID = c4d.DescID(c4d.DescLevel(c4d.mAXIS_X, c4d.DTYPE_REAL, 0))
                  # descID = c4d.DescID( c4d.DescLevel( c4d.ID_BASEOBJECT_ROTATION, c4d.DTYPE_VECTOR, 0), c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, 0) )
                  xTrck = c4d.CTrack( currentTAG, descID )
                  xCrv = xTrck.GetCurve()
          
          
          # Plug-in initialisation and declaration
          if __name__ == "__main__":
              # Load the plugin icon
              icon_absolute_path = os.path.join(os.path.dirname(__file__), 'res/icons', 'icon.png')
              plugin_icon = c4d.bitmaps.BaseBitmap()
          
              if plugin_icon is None:
                  raise RuntimeError("Failed to load icon resource.")
              
              if plugin_icon.InitWith(icon_absolute_path)[0] != c4d.IMAGERESULT_OK: 
                  raise RuntimeError("Failed to initialize icon resource.")
          
              # Register the plugin
              plugins.RegisterTagPlugin(
                  id = mPLUGIN_ID,
                  str = mTAG_NAME,
                  g = mMover,
                  description = 'mMover',
                  info = c4d.TAG_EXPRESSION | c4d.TAG_VISIBLE | c4d.TAG_IMPLEMENTS_DRAW_FUNCTION | c4d.TAG_HIERARCHICAL,
                  icon = plugin_icon
              )
          

          This code show in console,

              GetTags <c4d.BaseTag object called mMover/mMover with ID 1059303 at 4892750336>
          

          No more error, but no CTrack created, at list it does not appears in the Dope Sheet; so I should still makes something wrong somewhere.
          Does my pointing is now correct or still wrong for my DescID?

          Does this helps to point the problem?

          Thanks in advance,
          Christophe

          ferdinandF 1 Reply Last reply Reply Quote 0
          • mocolocoM
            mocoloco
            last edited by mocoloco

            Small addition to my previous post, the CTrack seems exists but do not appears in the Dope Sheet, is that normal, and if so why?

            Doing this,

            def Execute(self, tag, doc, op, bt, priority, flags): 
                    print ( f"GetTags {op.GetTag( mPLUGIN_ID )}" )
                    currentTAG = op.GetTag( mPLUGIN_ID )
                    
                    descID = c4d.DescID(c4d.DescLevel(c4d.mAXIS_X, c4d.DTYPE_REAL, 0))
                    # descID = c4d.DescID( c4d.DescLevel( c4d.ID_BASEOBJECT_ROTATION, c4d.DTYPE_VECTOR, 0), c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, 0) )
                    xTrck = c4d.CTrack( currentTAG, descID )
                    xCrv = xTrck.GetCurve()
                    print(f"CTrack . { xTrck }")
            
            

            Returns,

            CTrack . <c4d.CTrack object called axis_X/Track with ID 5350 at 4892757504>
            

            If I'm creating keyframes, will they be visibles in the Dope Sheet viewer or will they remains invisible?

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

              Hi @mocoloco,

              I still don't get why I have to write c4d.mAXIS_X instead of mAXIS_X

              You do not have to but it is convenient. An ID ist just a number and integer symbols like c4d.ID_USERDATA are just aliases for such number. 700 and ID_USERDATA do the same thing. When you provide a resource file, here one for your tag, Cinema 4D automatically exposes all resource symbols in the c4d namespace. You can also just redefine that symbol yourself, it is all the same. But it is extra work 😉

              # These are all the same.
              bobIsYourUncle: int = 1550
              
              mMoverTag[c4d.mAXIS_X] = 1.0
              mMoverTag[1550] = 1.0
              mMoverTag[bobIsYourUncle] = 1.0
              

              No more error, but no CTrack created, at list it does not appears in the Dope Sheet; so I should still makes something wrong somewhere. Does my pointing is now correct or still wrong for my DescID?

              Your DescID is correct, the problem is that you never add the track to your tag, you never call tag.InsertTrackSorted(xTrack). But you should not do this in TagData.Execute, as lined out below.

              First, as I was stuck on getting what I tried on Execute(), which was the place where I would like to set the CTrack, I indeed tried to get it set in the mMoverTagData.Init() - which I agree, is maybe not the right way to do.

              I never intended to imply that you should use TagData.Execute. You cannot add or remove elements from a scene as for example objects, tags, tracks or points in this method. Because you are outside of the main thread in Execute and bound to the threading restrictions. Init would be sort of okay, but a node has not yet been attached to a document when it is called, which probably would complicate things.

              But I think I understand what you want to do generally. Although I still do not understand why you want to create a track on your mAXIS_X parameter itself, which then leads to weird feedback loops when the parameter is used to both define the values of a track but is also driven by it.

              The other problem is that what you want to do in general, modify a scene from a NodeData plugin is not the greatest design pattern. Doing this will often lead to trouble in Cinema 4D. It is okay to do this in the form of a button and when the user clicks the button, something is added or removed. But in the dynamic fashion you want to do it, this is not a good path. If you want to dynamically generate animations based on a set of parameters, you should implement a CommandData plugin instead.

              I have provided nonetheless an example for both driving an x-position animation of an object hosted by a plugin tag with tracks, as well as driving the same animation procedurally. It is only the latter which is intended to be done with tags. If you want to to do this, you must offload your scene modifications to NodeData.Message, as this method is commonly called on the main thread. But you must still check for being there.

              Cheers,
              Ferdinand

              Plugin: pc14315.zip

              Result: I am first driving the animation of the cube procedurally and then in the second half with tracks. In both cases the x-axis position of the object hosting the tag is driven by the parameters in the tag.
              test.gif

              Code:

              """Implements a tag which drives the position of a host object procedurally or via tracks.
              
              The tag implements two modes, a "Procedural" and a "Track" mode. In procedural mode, the tag does
              what tags are supposed to do, it drives procedurally parameters of the scene, here the x-axis
              position of its host object. In track mode, it generates the same animation for its host object,
              but uses a key framed animation instead which is generated every time the user interacts with the
              GUI.
              
              Note:
                  It is not recommended to use NodeData plugins to add or remove elements (e.g., objects, 
                  materials, tags, shaders, tracks, keys, points, etc.) from a scene in the manner shown here. 
                  
                  It is okay to do this based on non-animateable parameters, e.g., a button. But as this is based
                  on a set of animateable parameters #P, one can run into problems. This can happen when the 
                  document is rendered and #P is also animated. The only safe way to add and remove elements are
                  therefore buttons in NodeData plugins.
              
                  Respected must be in any case the threading restrictions of TagData.Execute, it is never allowed
                  to add or remove scene elements from there. This solution will not crash, but is bound to the
                  main thread which can cause the plugin to (intentionally) stop operating in the mentioned 
                  scenario.
              """
              import c4d
              
              class mMoverTagData(c4d.plugins.TagData):
                  """Implements a tag which drives the position of a host object procedurally or via tracks.
                  """
                  # The plugin ID and a precise definition of the relative x-axis position parameter of an object.
                  ID_PLUGIN: int = 1059303
                  DID_OBJECT_REL_POSITION_X: c4d.DescID = c4d.DescID(
                      c4d.DescLevel(c4d.ID_BASEOBJECT_REL_POSITION, c4d.DTYPE_VECTOR, 0),
                      c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, 0))
                      
                  def Init(self, node: c4d.GeListNode) -> bool:
                      """Called to initialize a mMover tag instance.
              
                      Args:
                          node: The BaseTag instance representing this plugin object.
                      """
                      self.InitAttr(node, int, c4d.ID_MMOVER_MODE)
                      self.InitAttr(node, c4d.BaseTime, c4d.ID_MMOVER_ANIMATION_MIN)
                      self.InitAttr(node, c4d.BaseTime, c4d.ID_MMOVER_ANIMATION_MAX)
                      self.InitAttr(node, float, c4d.ID_MMOVER_AXIS_X_MIN)
                      self.InitAttr(node, float, c4d.ID_MMOVER_AXIS_X_MAX)
              
                      node[c4d.ID_MMOVER_MODE] = c4d.ID_MMOVER_MODE_PROCEDURAL
                      node[c4d.ID_MMOVER_ANIMATION_MIN] = c4d.BaseTime(0)
                      node[c4d.ID_MMOVER_ANIMATION_MAX] = c4d.BaseTime(1)
                      node[c4d.ID_MMOVER_AXIS_X_MIN] = 0.
                      node[c4d.ID_MMOVER_AXIS_X_MAX] = 1000.
              
                      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 when expressions are evaluated to let a tag modify a scene.
              
                      It is important to understand that tags are also bound by threading restrictions in this
                      method, we are here NOT on the main thread. I.e., we cannot add or remove nodes from the
                      scene as for example tracks. All we can do, is change parameters or the values of other
                      data. We can for example change the point positions of a point object, but we cannot add
                      or remove points.
              
                      Args:
                          tag: The BaseTag instance representing this plugin object.
                          op: The BaseObject hosting the tag.
                          doc: The document both the tag and object are attached to.
                      """
                      # Bail when the tag is not in procedural mode.
                      if tag[c4d.ID_MMOVER_MODE] != c4d.ID_MMOVER_MODE_PROCEDURAL:
                          return c4d.EXECUTIONRESULT_OK
              
                      # Set the x axis position of the tag host for the current time.
                      mg: c4d.Matrix = op.GetMg()
                      v: float = mMoverTagData._GetMoveOffset(tag, doc.GetTime())
                      mg.off = c4d.Vector(v, mg.off.y, mg.off.z)
                      op.SetMg(mg)
              
                      return c4d.EXECUTIONRESULT_OK
              
                  def Message(self, node: c4d.GeListNode, mid: int, data: object) -> bool:
                      """Called to signal events to the tag.
              
                      Used in this case to react to parameter changes, inducing track modifications on the host
                      object of the tag when the tag is in "Track" mode.
              
                      Args:
                          node: The BaseTag instance representing this plugin object.
                          mid: The ID of the raised event.
                          data: The data associated with the event.
                      """
                      # Bail when the tag is not attached to a document or an object as we will need both.
                      doc: c4d.documents.BaseDocument = node.GetDocument()
                      obj: c4d.BaseObject = node.GetObject()
                      if None in (doc, obj):
                          return True
              
                      # Bail when this is not an event for a parameter value being changed.
                      if mid != c4d.MSG_DESCRIPTION_POSTSETPARAMETER:
                          return True
              
                      # Get the DescID of the parameter which has changed and get the ID of the first desc level.
                      pid: int = data["descid"][0].id
              
                      # Bail when the mode of the tag is "Procedural" and it was not the mode which has changed.
                      isProceduralMode: bool = node[c4d.ID_MMOVER_MODE] == c4d.ID_MMOVER_MODE_PROCEDURAL
                      if pid != c4d.ID_MMOVER_MODE and isProceduralMode:
                          return True
                      
                      # Bail when this is not being called on the main thread, as all modifications we intend to
                      # do below require use being on the main thread (adding or removing tracks or keys).
                      if not c4d.threading.GeIsMainThreadAndNoDrawThread():
                          print ("Warning: Failed to execute 'mMover' tag logic due to threading restrictions.")
                          return True
                      
                      # The mode switched from "Track" to "Procedural", we attempt to remove an existing track.
                      if pid == c4d.ID_MMOVER_MODE and isProceduralMode:
                          mMoverTagData._RemoveTrack(obj)
                      # The user either turned on track mode or changed one of the parameters while being in
                      # track mode, we add or modify the track.
                      else:
                          mMoverTagData._AddOrModifyTrack(obj, node)
              
                      return True
                  
                  # --- Custom Methods ---------------------------------------------------------------------------
              
                  @staticmethod
                  def _GetMoveOffset(tag: c4d.BaseTag, t: c4d.BaseTime) -> float:
                      """Computes a new x-axis offset based on the data in #tag and the given time #t.
              
                      This is a custom method of this plugin and not part of the TagData interface.
              
                      Args:
                          tag: The tag to compute the offset for.
                          t: The time at which to compute the offset.
                      """
                      # Bail when the tag is not attached to a document.
                      doc: c4d.documents.BaseDocument = tag.GetDocument()
                      if not doc:
                          raise RuntimeError(f"{tag} is not attached to a document.")
              
                      # Get the min, max, and current frame for the animation described by the tag data.
                      fps: int = doc.GetFps()
                      minFrame: int = tag[c4d.ID_MMOVER_ANIMATION_MIN].GetFrame(fps)
                      maxFrame: int = tag[c4d.ID_MMOVER_ANIMATION_MAX].GetFrame(fps)
                      currentFrame: int = t.GetFrame(fps)
              
                      # Get the min and max offset values and compute with them and the time values the offset for
                      # the given time #t.
                      minValue: float = tag[c4d.ID_MMOVER_AXIS_X_MIN]
                      maxValue: float = tag[c4d.ID_MMOVER_AXIS_X_MAX]
                      v: float = c4d.utils.RangeMap(currentFrame, minFrame, maxFrame, minValue, maxValue, True)
              
                      return v
              
                  @staticmethod
                  def _AddOrModifyTrack(obj: c4d.BaseObject, tag: c4d.BaseTag) -> None:
                      """Adds or modifies the track for a relative x-position animation on #obj.
              
                      This is a custom method of this plugin and not part of the TagData interface.
              
                      Args:
                          obj: The object to add or modify the track for.
                          tag: The mMover tag carrying the data.
                      """
                      # Attempt to retrieve an existing track or create a new one.
                      track: c4d.CTrack = obj.FindCTrack(mMoverTagData.DID_OBJECT_REL_POSITION_X)
                      if track is None:
                          track = c4d.CTrack(obj, mMoverTagData.DID_OBJECT_REL_POSITION_X)
                          obj.InsertTrackSorted(track)
                      
                      # Flush the keys of the curve associated with the track in case we are dealing with an 
                      # already existing track.
                      curve: c4d.CCurve = track.GetCurve()
                      curve.FlushKeys()
              
                      # Get the time and animation values and write them into two keys.
                      minTime: c4d.BaseTime = tag[c4d.ID_MMOVER_ANIMATION_MIN]
                      maxTime: c4d.BaseTime = tag[c4d.ID_MMOVER_ANIMATION_MAX]
                      minValue: float = mMoverTagData._GetMoveOffset(tag, minTime)
                      maxValue: float = mMoverTagData._GetMoveOffset(tag, maxTime)
              
                      for time, value in ((minTime, minValue), (maxTime, maxValue)):
                          key: c4d.CKey = curve.AddKey(time)["key"]
                          if key:
                              key.SetValue(curve, value)
              
                  @staticmethod
                  def _RemoveTrack(obj: c4d.BaseObject) -> None:
                      """Removes the track for a relative x-position animation on #obj.
              
                      This is a custom method of this plugin and not part of the TagData interface.
              
                      Args:
                          obj: The object to attempt to remove the track from.
                      """
                      track: c4d.CTrack = obj.FindCTrack(mMoverTagData.DID_OBJECT_REL_POSITION_X)
                      if track:
                          track.Remove()
              
              
              def RegisterPlugins() -> None:
                  """Registers the plugins contained in this file.
                  """
                  if not c4d.plugins.RegisterTagPlugin(
                          id=mMoverTagData.ID_PLUGIN,
                          str="mMover Tag",
                          info=c4d.TAG_EXPRESSION | c4d.TAG_VISIBLE,
                          g=mMoverTagData, description="tmmover",
                          icon=c4d.bitmaps.InitResourceBitmap(c4d.ID_MODELING_MOVE)):
                      print("Warning: Failed to register 'mMover' tag plugin.")
              
              
              if __name__ == "__main__":
                  RegisterPlugins()
              

              MAXON SDK Specialist
              developers.maxon.net

              1 Reply Last reply Reply Quote 1
              • mocolocoM
                mocoloco
                last edited by

                Hi @ferdinand,

                I went a bit further yesterday and finally found the whole way to get it work.
                The code part is here below for the community.

                I'm still stuck on the force redraw of the GUI when creating CTrack or removing them. c4d.EventAdd() seems not working fine, and c4d.gui.GeUpdateUI() can only be invoked from main thread. Is that possible to force a GUI redraw from a TAG?

                This is the excerpt from the whole code for the CTrack only.
                You have to use this code by adapting it in your own context

                # Create CTrack when not available
                currentTAG = op.GetTag( mPLUGIN_ID )
                descID = c4d.DescID(c4d.DescLevel(c4d.mAXIS_X, c4d.DTYPE_REAL, 0))
                if currentTAG.FindCTrack(descID) is None:
                     trck_X = c4d.CTrack( currentTAG, descID )
                     currentTAG.InsertTrackSorted(trck_X)
                     print( "CTrack created" )
                     c4d.EventAdd()
                
                
                # Remove all the CTracks
                currentTAG = op.GetTag( mPLUGIN_ID )
                if (len (currentTAG.GetCTracks()) > 0):
                       for track in currentTAG.GetCTracks():
                              track.FlushData()
                              track.Remove()
                              print( "CTracks removed" )
                

                Thanks,
                Christophe

                1 Reply Last reply Reply Quote 0
                • mocolocoM
                  mocoloco
                  last edited by

                  Hi @ferdinand

                  I haven't refreshed the thread prior posting my last message, so I missed your reply with the whole explanation and example. Thanks a lot, this fulfil the missing gap.

                  Thanks a lot,
                  Christophe

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

                    Hey @mocoloco,

                    good morning 🙂

                    Is that possible to force a GUI redraw from a TAG?

                    You are bound there to the same threading restrictions as with adding or removing things. But other than for modifying the scene, which Cinema 4D will carry out outside of the main thread and then possibly crash, raising events, as for example a redraw event, is not possible at all from outside of the main thread. The first thing methods like c4d.DrawViews do, is check for being on the main (drawing) thread and when they are not, they get out.

                    So, it is possible, but you must be on the main thread. This will never happen on TagData.Execute, but it will on TagData.Message for example. So you can force a redraw when a user interacted with a parameter for example.

                    Cheers,
                    Ferdinand

                    MAXON SDK Specialist
                    developers.maxon.net

                    1 Reply Last reply Reply Quote 0
                    • mocolocoM
                      mocoloco
                      last edited by mocoloco

                      Good morning @ferdinand!

                      I modified the whole code to always do my changes inside TagData.Message now instead of doing them in TagData.Execute, which should prevent any crash - even if I didn't got one, it seems far better to stick on the good rules and habit.

                      Have a good day!
                      Christophe

                      1 Reply Last reply Reply Quote 0
                      • DunhouD Dunhou referenced this topic on
                      • First post
                        Last post