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
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Editable Object Plugin returns Null after scaling

    Cinema 4D SDK
    python
    2
    4
    58
    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.
    • JH23J
      JH23
      last edited by

      I'm creating a basic ObjectData plugin that returns a cube with a modified scale. However, when making the object editable (C key), sometimes the result becomes a Null object with the cube inside, especially after changing the scale.

      import c4d
      from c4d import plugins, utils
      
      class MyObjectPlugin(plugins.ObjectData):
      
          def Init(self, op):
              return True
      
          def GetVirtualObjects(self, op, hierarchyhelp):
              dirty = op.CheckCache(hierarchyhelp) or op.IsDirty(c4d.DIRTY_DATA)
              if not dirty:
                  return op.GetCache(hierarchyhelp)
      
              cube = c4d.BaseObject(c4d.Ocube)
              if cube is None:
                  return None
      
              cube[c4d.ID_BASEOBJECT_REL_SCALE, c4d.VECTOR_X] = 2.0
              cube[c4d.ID_BASEOBJECT_REL_SCALE, c4d.VECTOR_Y] = 1.0
              cube[c4d.ID_BASEOBJECT_REL_SCALE, c4d.VECTOR_Z] = 0.5
      
              return cube
      
      

      Am I doing something wrong? My intention is specifically that when making the generator editable, the result is the scaled object, but without the null object that is generated when modifying it, and without modifying the generator scale.

      Thanks in advance for any help!

      PS: I would also like to know if there is a specific way to return several objects or a specific one when making the generator editable, making it more customizable.

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

        Hello @JH23,

        Thank you for reaching out to us. Your code is looks correct, but you should not return None on a critical error (e.g., when you run out of memory to allocate things), but return c4d.BaseObject(c4d.Onull) instead, that is also what Cinema 4D will do, when it cannot make sense out of your method. E.g. it should be:

        if not(cube := c4d.BaseObject(c4d.Ocube)):
            return c4d.BaseObject(c4d.Onull)
        
        cube[c4d.ID_BASEOBJECT_REL_SCALE, c4d.VECTOR_X] = 2.0
        ...
        

        Also, does your object have child inputs it depends on? E.g., like an Extrude object which depends on a child spline? I assume it does not, right? You should then change your manual dirty check to this, because your code also tries to build the children of this node which could lead to issues.

        # Only build the cache of the object when one of its parameters has changed or when there is no cache.
        if not op.IsDirty(c4d.DIRTY_DATA) and op.GetCache():
            return op.GetCache() # you could also pass the hh here, but that is not necessary in this case
        

        Do you see any errors in the console when this happens? Cinema 4D returning a null object for a cache means that building the cache failed. There must be somewhere a bug in your code, I am not sure though that my suggestions will fix it.

        I would also like to know if there is a specific way to return several objects or a specific one when making the generator editable, making it more customizable.

        A cache must always terminate into a singular object because it is just part of an object hierarchy. When you want your cache to contain multiple things, you must parent them to a null and return that null as your cache.

        As to reacting to CTSO, yes that is possible, but not in Python. When an object is being built for CSTO, its HierachyHelp->GetFlags() will be BUILDFLAGS::EXTERNALRENDERER | BUILDFLAGS::ISOPARM but HierachyHelp has not been wrapped for Python. There is MSG_CURRENTSTATE_END emitted, and you can capture this in Python too via NodeData::Message, but that is only the signal that CSTO has finished (and that you might want to revert to a non-CSTO specialized cache).

        But in general, we advise against such trickery where objects switch out their cache in certain contexts. It is valid to do this, but often requires intimate knowledge of the API to be done sucessfully.

        Cheers,
        Ferdinand

        MAXON SDK Specialist
        developers.maxon.net

        1 Reply Last reply Reply Quote 0
        • JH23J
          JH23
          last edited by

          Hi @ferdinand,
          Apologies for the lack of context earlier. I didn’t mean that it returns null, but rather that it returns an object inside a null container. When I try to modify the scale of the resulting object, this behavior persists. I’ve noticed a similar pattern when working with Python generators.

          import c4d
          from c4d import utils
          def Cube():
              obj = c4d.PolygonObject(8, 6)
          
              obj.SetPoint(0, c4d.Vector(-100.0,-100.0,-100.0))
              obj.SetPoint(1, c4d.Vector(-100.0,100.0,-100.0))
              obj.SetPoint(2, c4d.Vector(100.0,-100.0,-100.0))
              obj.SetPoint(3, c4d.Vector(100.0,100.0,-100.0))
              obj.SetPoint(4, c4d.Vector(100.0,-100.0,100.0))
              obj.SetPoint(5, c4d.Vector(100.0,100.0,100.0))
              obj.SetPoint(6, c4d.Vector(-100.0,-100.0,100.0))
              obj.SetPoint(7, c4d.Vector(-100.0,100.0,100.0))
          
              polygon1 = c4d.CPolygon( 3,2,0,1)#front
              polygon2 = c4d.CPolygon( 5,4,2,3)#left
              polygon3 = c4d.CPolygon( 7,6,4,5)#back
              polygon4 = c4d.CPolygon( 1,0,6,7)#right
              polygon5 = c4d.CPolygon( 2,4,6,0)#down
              polygon6 = c4d.CPolygon( 5,3,1,7)#up
          
              obj.SetPolygon(0, polygon1)
              obj.SetPolygon(1, polygon2)
              obj.SetPolygon(2, polygon3)
              obj.SetPolygon(3, polygon4)
              obj.SetPolygon(4, polygon5)
              obj.SetPolygon(5, polygon6)
          
              polygon_count = obj.GetPolygonCount()
              uvw_tag = obj.MakeVariableTag(c4d.Tuvw,polygon_count)
              for i in range(uvw_tag.GetDataCount()):
                  uvwdict = uvw_tag.GetSlow(i)
                  p1 = c4d.Vector(1,0,0)
                  p2 = c4d.Vector(1,1,0)
                  p3 = c4d.Vector(0,1,0)
                  p4 = c4d.Vector(0,0,0)
                  uvw_tag.SetSlow(i, p1, p2, p3, p4)
          
              obj.SetPhong(True, 1, utils.Rad(40.0))
              obj.Message(c4d.MSG_UPDATE)
              return obj
          def main():
              obj = Cube()
              scale = 2
              obj[c4d.ID_BASEOBJECT_REL_SCALE,c4d.VECTOR_X] = scale
              obj[c4d.ID_BASEOBJECT_REL_SCALE,c4d.VECTOR_Y] = scale
              obj[c4d.ID_BASEOBJECT_REL_SCALE,c4d.VECTOR_Z] = scale
              return obj
          

          I’d like to understand why this behavior occurs, and— as I mentioned earlier— if there's a way to define custom behavior for CTSO within a plugin. Once again, sorry for the confusion caused by my previous message.

          Just to clarify, modifying the polygon's scale by adjusting its vertices is not an option, since I want the polygon to be generated with those specific attributes already modified.

          Thanks for your support,
          James H.

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

            Hey @JH23,

            there is no need to apologize for a lack of context. Now that I read your first posting again, it is actually perfectly clear what you are asking for. I just did not read it properly, my bad, sometimes this happens when I am in a hurry.

            The answer to your question is not trivial, but the TLDR is that you have only little control over it.

            There exist two commands, 'Current State to Object' (CSTO) and 'Make Editable' (ME). CSTO is basically the less aggressive version of ME, where it runs through all the elements of a cache of something and tries to collapse them gracefully (which could result in output that is still collapsible itself). ME more aggressively flattens the cache hierarchy.

            For both commands, at the very basic level, there is the distinction between generators and and non-generators. When you CSTO some generator object which just holds another generator in its cache (imagine your GetVirtualObjects or main just returning c4d.BaseObject(c4d.Ocube)), it will just return a copy of that generator (which is still a generator, i.e., something that has a cache). But when your generator returns a non-generator, i.e., a discrete PolygonObject, it will wrap the copy of this cache in a null when returning it.

            But that is not all of it. Because, there are also functions such as TransferDynamicProperties and OptimizeHierarchy which ensure that relevant scene data is kept and the output is as compact as possible. They run after ME/CSTO by further manipulating the ME/CSTO output. The original MEed/CSTOed object might have had a transform which must be copied onto the flattened output, so that it has the same transform in world space. Objects often also hold hidden data in form of tags in Cinema 4D which might have to be copied from the original object onto the flattened result. This all might lead to the functions either removing unnecessary null objects which before have been created by ME/CSTO or adding new ones.

            That your scale manipulations result in an extra null is a bit surprising, but that must have to do with the transform normalization code which runs after ME/CSTO. At first I thought, it might transfer the scale to the null object, so that it can normalize the scale of the cache. But that is not the case, it is still the cache object which has the scale 2 when you CSTO/ME your generator. I would really have to debug this in detail, to find out why exactly this happens.

            But in general, I would advise against scaling the matrix/transform of an object, and instead, apply the scale to the points of a discrete PolygonObject or the parameters of a BaseObject itself. Transforms which have axis components of non-unit length (i.e., a scale != 1.0), often lead to problems.

            The important message is that even when you find a solution which gives you the desired flat cache for this problem (by for example scaling the points), there could be countless other scenarios such as a user having a MoGraph tag on your generator or something like that, where then MEing or CSTOing would result in a null + cache output. You should not build your code on the assumption that the MEed or CSTOed output of your generator will always be flat (or the opposite), because you simply cannot guarantee that.

            Not the most satisfying answer, I know, but I hope it helps to clarify the situation.

            Cheers,
            Ferdinand

            Here is how you could post process points with a transform:

            def main():
                obj: c4d.PointObject = Cube()
            
                # We could of course also just pass this #transform into your #Cube function, to do it right 
                # there. But we are making a point of doing this as a post processing step (so that we can do
                # this at any point in time). We scale all points by a factor of 2 and rotate them 45° on the 
                # Y axis in the coordinate system of the object. When we now CTSO your object, we have a flat
                # output.
                transform: c4d.Matrix = (c4d.utils.MatrixScale(c4d.Vector(2, 2, 2)) * 
                                         c4d.utils.MatrixRotY(c4d.utils.DegToRad(45)))
                obj.SetAllPoints([n * transform for n in obj.GetAllPoints()])
                obj.Message(c4d.MSG_UPDATE)
            
                return obj
            

            MAXON SDK Specialist
            developers.maxon.net

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