Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush Python 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
    • Recent
    • Tags
    • Users
    • Register
    • Login

    Best way to hide a child and get best perfomance

    Scheduled Pinned Locked Moved Cinema 4D SDK
    2025pythonwindows
    7 Posts 3 Posters 25 Views 2 Watching
    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.
    • T Offline
      Tpaxep
      last edited by Tpaxep

      Hi, forum!
      I'm working on a tool the principle of operation of which is similar like a "Sweep". It wraps one "profile" spline around a "path" spline. I want any geometric change to these splines to trigger an update. I think I've figured it out using this code.

      def GetVirtualObjects(self, op, hh):
         
          doc = op.GetDocument()
          path = op.GetDown()
          if path is None:
              return None
      
          profile = path.GetNext()
          if profile is None:
              return None
      
          dirty = op.CheckCache(hh)\
                  or op.IsDirty(c4d.DIRTY_DATA)\
                  or path.IsDirty(c4d.DIRTY_DATA)\
                  or path.IsDirty(c4d.DIRTY_MATRIX)\
                  or profile.IsDirty(c4d.DIRTY_DATA)\
                  or profile.IsDirty(c4d.DIRTY_MATRIX)
          if not dirty: return op.GetCache(hh)
      
      
          path = c4d.utils.SendModelingCommand(
              c4d.MCOMMAND_CURRENTSTATETOOBJECT,
              [path],
              c4d.MODELINGCOMMANDMODE_ALL,
              c4d.BaseContainer(),
              doc
          )[0]
      
          profile = c4d.utils.SendModelingCommand(
              c4d.MCOMMAND_CURRENTSTATETOOBJECT,
              [profile],
              c4d.MODELINGCOMMANDMODE_ALL,
              c4d.BaseContainer(),
              doc
          )[0]
      

      However, I'd like to hide the child elements, but I can't figure out how I can do it more correctly

      I have try use c4d.BaseObject.Touch() and my child is hide, but I can't edit my path/profile dynamically. If I edit the path/profile, I need to refresh the viewport or op settings to see the changes.

      I tried this code. Perhaps I didn't fully understand something and missed something.

      path = op.GetDown()
      if path is None:
          return None
      
      profile = path.GetNext()
      if profile is None:
          return None
      
      path.Touch()
      profile.Touch()
      
      dirty = op.CheckCache(hh)\
          or op.IsDirty(c4d.DIRTY_DATA)\
          or path.IsDirty(c4d.DIRTY_DATA)\
          or path.IsDirty(c4d.DIRTY_MATRIX)\
          or profile.IsDirty(c4d.DIRTY_DATA)\
          or profile.IsDirty(c4d.DIRTY_MATRIX)
      if not dirty: return op.GetCache(hh)
      

      Besides this, I tried using op.GetAndCheckHierarchyClone like it was in this thread https://developers.maxon.net/forum/topic/10491/13941_hide-child-of-object-plugin/3

      path = op.GetDown()
      if path is None:
          return None
      
      profile = path.GetNext()
      if profile is None:
          return None
      
      ProfileClone = op.GetAndCheckHierarchyClone(hh, profile, c4d.HIERARCHYCLONEFLAGS_ASSPLINE, False)
      
      ProfileCloneDirty = ProfileClone["dirty"]
      ProfileCloneClone = ProfileClone["clone"]
      
      PathClone = op.GetAndCheckHierarchyClone(hh, path, c4d.HIERARCHYCLONEFLAGS_ASSPLINE, False)
      
      PathCloneDirty = PathClone["dirty"]
      PathCloneClone = PathClone["clone"]
      
      if not ProfileCloneDirty:
          return ProfileCloneClone
      
      if not PathCloneDirty:
          return PathCloneClone
      
      path_upd = c4d.utils.SendModelingCommand(
          c4d.MCOMMAND_CURRENTSTATETOOBJECT,
          [PathCloneClone],
          c4d.MODELINGCOMMANDMODE_ALL,
          c4d.BaseContainer(),
          doc
      )[0]
      
      profile_upd = c4d.utils.SendModelingCommand(
          c4d.MCOMMAND_CURRENTSTATETOOBJECT,
          [ProfileCloneClone],
          c4d.MODELINGCOMMANDMODE_ALL,
          c4d.BaseContainer(),
          doc
      )[0]
      

      However, this reduces performance compared to the code above. I would be very grateful if you could help me figure out how I can do this more correctly and optimized.

      P.s I think my question will seem primitive, but I'm just learning the API, so don't judge me too harshly πŸ™‚

      ferdinandF ThomasBT 2 Replies Last reply Reply Quote 0
      • ferdinandF Offline
        ferdinand @Tpaxep
        last edited by ferdinand

        Hey @Tpaxep,

        Thank you for reaching out to us. There are few things to unpack here.

        Hiding Input Objects

        Input objects are hidden automatically when you implement an ObjectData plugin that uses the flag OBJECT_INPUT and 'touches' its input objects, by for example calling GetAndCheckHierarchyClone on them as you do. This only works for direct children of the object, not grandchildren or even deeper descendants.

        Under the hood, this causes the input objects to have the flag BIT_CONTROLOBJECT to be set and then being touched which among other things will delete their cache, resulting in them becoming invisible. Technically you can do parts of this yourself (to hide also objects that are not direct children of your object) but:

        • Cinema 4D evaluates its scene graph hierarchically top down. This is also why you need GetAndCheckHierarchyClone so that you can build the cache of your descendants which has not been build yet when you are being built because fundamentally your child objects come after you in a hierarchy. I cannot unpack this in all detail, but trying to sidestep the cache building logic of Cinema 4D is not recommended and not trivial. Your inputs should always be direct children of yourself, otherwise you will land in a world of hurt sooner or later.
        • 'Hiding' inputs is actually deleting them. BaseObject.Touch literally marks the cache of an object for deletion and BIT_CONTROLOBJECT then later prevents the object from rebuilding it when it is its turn to naturally build the cache. No cache means there is nothing for the object to draw into the viewport. You can technically also hide objects with things like NBIT_OHIDE, but there you run into threading issues (see the section below) and also update issues. When your plugin does miss some case, the user ends up with a permanently hidden object in his or her scene, destroying his or her work. So, using NBIT_OHIDE in this context is not a good idea.

        Threading Restrictions

        Your code violates the threading restrictions of Cinema 4D. You run a modelling command in the GVO of your object plugin directly on an element attached to a loaded (in this case active) scene and with that risk crashes and loss of data. The subject is also explicitly being discussed for modelling commands in this code example (also read the file doc string as it explains some things).

        In general, you cannot change the state of scene elements from any threaded function such as ObjectData.GetVirtualObjects as this will sooner later lead to crashes. Functions such as GetVirtualObjects or ObjectData.GetContour will always only be called in parallel. Functions such as NodeData.Message might be called from the main thread (you can check with c4d.threading.GeIsMainThread) and therefore you can then manipulate the scene from there. E.g., implement a button in an object or tag plugin UI that inserts another object, deletes something, etc.

        Cheers,
        Ferdinand

        MAXON SDK Specialist
        developers.maxon.net

        1 Reply Last reply Reply Quote 0
        • ThomasBT Offline
          ThomasB @Tpaxep
          last edited by ThomasB

          @Tpaxep
          besides that I have a code which works for your scenario.

          def GetVirtualObjects(self, op, hh):
          
              profile_orig = op.GetDown()
              if profile_orig is None:
                  return None
          
              path_orig = profile_orig.GetNext()
              if path_orig is None:
                  return None
          
              # you need to make clones of the children, don't use the orig
              # first make clones and then use the GACHC Methode below
              profile = profile_orig.GetClone()
              path = path_orig.GetClone()
          
              dirty = op.CheckCache(hh) or op.IsDirty(c4d.DIRTYFLAGS_ALL)
          
              child_dirty = op.GetAndCheckHierarchyClone(hh, profile_orig, c4d.HIERARCHYCLONEFLAGS_ASSPLINE, True)         
          
              if not any([dirty, child_dirty["dirty"]]):
                  return child_dirty["clone"]
          
              print("GVO Executed")
              # calculation of your geometry
              sweep = c4d.BaseObject(c4d.Osweep)
              tag = c4d.BaseTag(c4d.Tphong)
              tag[c4d.PHONGTAG_PHONG_USEEDGES] = False
              sweep.InsertTag(tag)
              path.InsertUnder(sweep)
              profile.InsertUnder(sweep)
          
              return sweep
          

          you can also track dirty manually:

          def GetVirtualObjects(self, op, hh):
          
                  profile_orig = op.GetDown()
                  if profile_orig is None:
                      return None
          
                  path_orig = profile_orig.GetNext()
                  if path_orig is None:
                      return None
          
                  profile = profile_orig.GetClone()
                  path = path_orig.GetClone()
          
                  dirty = op.CheckCache(hh) or op.IsDirty(c4d.DIRTYFLAGS_ALL)
          
                  #Manually track dirty of the childs (Example)      
                  child_dirty = False
                  for child in op.GetChildren():
                      child_dirty = child.IsDirty(c4d.DIRTYFLAGS_DATA | c4d.DIRTYFLAGS_MATRIX)
                      if child_dirty:
                          break
          
                  # After Dirty Touch the childs
                  profile_orig.Touch()
                  path_orig.Touch()
          
                  if not any([dirty, child_dirty]):
                      return op.GetCache(hh)    
          
                  print("GVO Executed")
                  # calculation of your geometry
                  sweep = c4d.BaseObject(c4d.Osweep)
                  tag = c4d.BaseTag(c4d.Tphong)
                  tag[c4d.PHONGTAG_PHONG_USEEDGES] = False
                  sweep.InsertTag(tag)
                  path.InsertUnder(sweep)
                  profile.InsertUnder(sweep)
          
                  return sweep
          

          Thanks,
          T.B

          1 Reply Last reply Reply Quote 1
          • ferdinandF Offline
            ferdinand
            last edited by ferdinand

            Regrading @ThomasB snippets, in the context of a GVO call I would curate my C4DAtom.GetClone more, e.g., with COPYFLAGS_NO_HIERARCHY, COPYFLAGS_NO_ANIMATION, or even COPYFLAGS_NO_BRANCHES. Copying a full node can otherwise be quite costly because it is not just "a node" but a whole subset of the scene graph with all the things which are attached to it.

            And that is exactly the problem I talk about in the SMC example I linked to above: Copying can be expensive. And in the broadest scenario you will always need everything, because dependencies can be complex. And even go beyond a pure "down"-dependency (with for example base link fields) where this copying approach then fails.

            • COPYFLAGS_NO_HIERARCHY: Means no children or deeper hierarchical descendants are copied.
            • COPYFLAGS_NO_ANIMATION: Means that no animation tracks or keyframes are copied (which are a form of branches).
            • COPYFLAGS_NO_BRANCHES: Means that no branches are copied. Children are not branches but pretty much everything else is. tracks, curves, tags, special data, etc., they will be all lost. Editable polygon- and spline objects store for example their point, tangent, and polygon data as tags, so you would loose all that. But it can make sense to use this on generator objects (e.g., "Cube Object" or "Circle Spline") to strip them of everything, as they are self contained capsules.

            For @ThomasB case it is probably more performant, to just build the caches for the inputs, i.e., the splines. Which will include all the crazy MoGraph fields and deformer dependencies with which splines could be modified, and then just use that cache as the input for the sweep. The caches of SplineObject instances will be LineObject instances, which is a little bit wrong to put them under a sweep. So, to make it perfect, you would just grab all the points and segments of the line object and re-express them as a new linear spline object.

            MAXON SDK Specialist
            developers.maxon.net

            1 Reply Last reply Reply Quote 1
            • T Offline
              Tpaxep
              last edited by

              Thanks for the answers! The method works great, but I ran into a problem when running the code further. My next part looks like this

                      spline = path.GetRealSpline()
                      profile_spline = profile.GetRealSpline()
                      if spline is None or profile_spline is None:
                          return None
              
                      points_profile = profile_spline.GetAllPoints()
                      count_profile = len(points_profile)
                      is_profile_closed = profile_spline.IsClosed()
                      if count_profile < 2:
                          return None
              
                      sh = SplineHelp()
                      if not sh.InitSplineWith(
                          spline,
                          c4d.SPLINEHELPFLAGS_GLOBALSPACE
                          | c4d.SPLINEHELPFLAGS_USERDEFORMERS
                          | c4d.SPLINEHELPFLAGS_CONTINUECURVE
                      ):
                          return None
              

              With this approach, my path is no longer processed as GetRealSpline, while the profile is processed as expected.

              I adjusted the code slightly, integrating the def GetCloneSpline function from this code.

              Now I get this

                      profile_orig = op.GetDown()
                      if profile_orig is None:
                          return None
              
                      path_orig = profile_orig.GetNext()
                      if path_orig is None:
                          return None
              
                      dirty = op.CheckCache(hh) or op.IsDirty(c4d.DIRTYFLAGS_ALL)
              
                      profileData = op.GetAndCheckHierarchyClone(
                          hh,
                          profile_orig,
                          c4d.HIERARCHYCLONEFLAGS_ASSPLINE,
                          True)
              
                      if not any([dirty, profileData["dirty"]]):
                          return profileData["clone"]
              
              
                      profile = GetCloneSpline(profile_orig)
                      path = GetCloneSpline(path_orig)
              

              Maybe I have missed something important (let me know), but overall everything works like i want. But I have a few questions

              1. Now I'm using CSTO as a quick fix for the GetRealSpline issue, but it seems incorrect. I'd like to know which way was better:
                use CSTO outside of GVO (using a temporary document, as in the example) for improvement, or if I should try to find an alternative solution?

              2. I didn't fully understand this message: is it better to use caches instead of cloning, or should I use both in combination?

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

                I do not quite understand your problem and more importantly what your goal is. Where does 'my path is no longer processed as GetRealSpline' happen? I assume with 'processed' you mean that you call BaseObject.GetRealSpline on something?

                1. Using CSTO as a stand-in for GetRealSpline sounds like a bad idea.
                2. It is better to use caches when possible, because there you do not have to copy whole documents to ensure that all dependencies are included. But you still have to copy something (the cache) but that is less expensive. When the whole thing happens for splines, the issue is that splines return LineObject and not SplineObject as their cache. See here for details. You can sort of just ignore the issue and treat the LineObject instances as if they were SplineObject instances, but that might cause issues here and there. Some tools/objects such as the Extrude Object also accept LineObject inputs even though a user can never manually create them. But there is no guarantee that everything that can deal with SplineObject instances as inputs also supports LineObject.

                MAXON SDK Specialist
                developers.maxon.net

                T 1 Reply Last reply Reply Quote 0
                • T Offline
                  Tpaxep @ferdinand
                  last edited by

                  Here's my prototype, if you are interested in my goal

                  Right now, everything works as expected, but only with these lines of code.

                  profile = GetCloneSpline(profile_orig)
                  path = GetCloneSpline(path_orig)
                  

                  I'm happy with the current result. So, I'd love to get some additional advice on correctness and optimization. I think your previous answer was comprehensive enough, so I'll try to integrate some of it.
                  But I'd also be very grateful if you could take a look at my plugin and perhaps give me some more specific optimization tips. If that's not too much trouble, of course!πŸ‘‰ πŸ‘ˆ
                  😁

                  @ferdinand @ThomasB
                  Anyway, thanks for your replies and advices!β™₯

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