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

    Parameter Description Access not working as expected

    Cinema 4D SDK
    python r20
    3
    14
    2.2k
    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.
    • ferdinandF
      ferdinand
      last edited by

      Hi,

      welcome to the forum. If I understand your posting correctly, your question is how to test if a description parameter has been modified in respect to its default value. Your code is almost there, but you probably misconstrued c4d.DESC_DEFAULT as a boolean indicating a changed value, but it is actually the actual default value. So to test if the parameter has been modified, it would be something like this:

      description = node.GetDescription(c4d.DESCFLAGS_DESC_0)
      for bc, descid, _ in description:
          # Test if the current value is identical to the default value.
          is_default = bc[c4d.DESC_DEFAULT] == node[descid]
      

      Cheers,
      zipit

      MAXON SDK Specialist
      developers.maxon.net

      davidtornoD 1 Reply Last reply Reply Quote 1
      • davidtornoD
        davidtorno @ferdinand
        last edited by

        @zipit said in [python] Parameter Description Access not working as expected:

        Your code is almost there, but you probably misconstrued c4d.DESC_DEFAULT as a boolean indicating a changed value, but it is actually the actual default value.

        Correct, I understood it as indicating a changed value. Good to know. These are the nuances I am still adjusting to with C4D python. Thanks for the answer and explanation. I will give this a go tomorrow when I’m back on my machine. I appreciate your help.

        --
        David Torno
        https://fendrafx.com
        https://vimeo.com/davidtorno

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

          Hi,

          after reading your reply I realised, that I probably should have also mentioned that Cinema has a dirty system, which will let you check if an object has been modified. It is scattered over multiple classes, but most of it is attached to C4DAtom, with the major method being C4DAtom.GetDirty.

          Cheers,
          zipit

          MAXON SDK Specialist
          developers.maxon.net

          davidtornoD 1 Reply Last reply Reply Quote 0
          • davidtornoD
            davidtorno @ferdinand
            last edited by davidtorno

            @zipit So I have tried implementing your check for DESC_DEFAULT and I am still not getting values for default.

            #.....Snippet from my script, full code at bottom of post.
                    #Process tag parameters
                    desc = o.GetDescription(c4d.DESCFLAGS_DESC_0)
                    for bc, paramid, groupid in desc:
                        
                        #Check if current value matches default value
                        is_default = bc[c4d.DESC_DEFAULT] == o[paramid]
                        
                        #print both values for visual comparison
                        print "DEBUG:: " + str(bc[c4d.DESC_DEFAULT]) + " == " + str(o[paramid]) + " --> " + str(is_default)
                        
                        if is_default is not True:
            #.....
            

            As an example I am currently using a simple Null object for testing the default functionality. Changing only the Object > Display parameter to see if my code catches if it is set to default or not. The print output is like this so far. For simplicity I've only posted the Object Properties results.

            Object Properties
            DEBUG:: None == 0 --> False
            	Display	0
            DEBUG:: None == 10.0 --> False
            	Radius	10.0
            DEBUG:: None == 10.0 --> False
            	Aspect Ratio	10.0
            DEBUG:: None == 0 --> False
            	Orientation	0
            

            I would expect this debug result to read as this since all parameters are at their defaults.

            Object Properties
            DEBUG:: 0 == 0 --> True
            DEBUG:: 10.0 == 10.0 --> True
            DEBUG:: 10.0 == 10.0 --> True
            DEBUG:: 0 == 0 --> True
            #...
            

            ...but bc[c4d.DESC_DEFAULT] is continuously returning None for some reason. What am I missing here? Thanks again for any assistance on this.

            Here is my full script code currently. I cleaned up a lot from previous build as it was getting very messy. The param name/value retrieval is now it's own function (def):

            import c4d
            from c4d import gui
            
            # Main function
            def main():
                o = op
                if o is not None:
                    recurse(o)
            
            def getParamData(o):
                try:
                    #misc var
                    grpName = ""
                    
                    #Process tag parameters
                    desc = o.GetDescription(c4d.DESCFLAGS_DESC_0)
                    for bc, paramid, groupid in desc:
                        
                        #Check if current value matches default value
                        is_default = bc[c4d.DESC_DEFAULT] == o[paramid]
                        
                        #print both values for visual comparison
                        print "DEBUG:: " + str(bc[c4d.DESC_DEFAULT]) + " == " + str(o[paramid]) + " --> " + str(is_default)
                        
                        if is_default is not True:
                            isgroup = paramid[0].dtype==c4d.DTYPE_GROUP
                            if isgroup:
                                #handle blank names
                                if bc[c4d.DESC_NAME] is not "":
                                    grpName = bc[c4d.DESC_NAME]
                                    print grpName
                    
                            #check for param name and ident
                            if bc[c4d.DESC_NAME] and bc[c4d.DESC_IDENT]:
                                try:
                                    #Adjust color float to 255 range
                                    vec = o[paramid]
                                    if "Color" in bc[c4d.DESC_NAME] and "Vector" in str(vec):
                                        vec = o[paramid]
                                        val = "Vector(" + str(vec[0]*255) + ", " + str(vec[1]*255) + ", " + str(vec[2]*255) + ")"
                                    else:
                                        #Strip None values out
                                        blank = o[paramid]
                                        if blank == None:
                                            val = ""
                                        else:
                                            val = str(o[paramid])
                                    
                                    #handle group name duplicates in results
                                    if bc[c4d.DESC_NAME] == grpName:
                                        pass
                                    else:
                                        print "\t" + bc[c4d.DESC_NAME] + "\t" + val
                                except:
                                    print "\t" + bc[c4d.DESC_NAME] + ": PARAMETER REQUIRES MANUAL EXTRACTION"
                
                except:
                    print "--UNKNOWN PARAMETER--"
            
            
            def recurse(o):
                obj = o
                if obj is not None:
                    #Misc variables
                    tab = "\t"
            
                    #Header
                    print "Name\tParameter\tValue\tParam ID"
            
                    #Print object name
                    on = obj.GetName()
                    print on
            
                    #Get object data
                    #data = obj.GetData()
            
                    #Get param name and value
                    getParamData(obj)
            
                    #Get object tags
                    tags = obj.GetTags()
                    
                    #Loop tags
                    for t in tags:
                        tn = t.GetTypeName()
                        if tn is not "" or None:
                            #Add spacer in output
                            print ""
            
                            #Get tag name
                            print t.GetTypeName()
                            
                            #Get param name and value
                            getParamData(t)
            
            
                    #Check for child object
                    d = obj.GetDown()
                    if d is not None:
                        recurse(d)
            
                    #Get next object
                    n = obj.GetNext()
                    if n is not None:
                        recurse(n)
                else:
                    print "Error: Select one starting object first."
            
            # Execute main()
            if __name__=='__main__':
                main()
            

            --
            David Torno
            https://fendrafx.com
            https://vimeo.com/davidtorno

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

              Hi,

              the problem you are running into, is that the default value is an optional description element flag. If no such flag has been provided by the author who wrote the description markup, the element will fall back to the internal default value of that type (e.g. 0 for an integer). This value however won't be reflected in the DESC_DEFAULT field of a Description object, it will return None in this case, like you experienced it in your tests.

              You would have to either handle that case manually or use the dirty checksums that I did mention. The problem there is that you can test the data container of a node for being dirty, i.e. its parameters, and by that detect if a parameter has been modified, but not exactly which one of the parameters has been modified.

              Cheers,
              zipit

              MAXON SDK Specialist
              developers.maxon.net

              davidtornoD 1 Reply Last reply Reply Quote 1
              • davidtornoD
                davidtorno @ferdinand
                last edited by

                @zipit said in Parameter Description Access not working as expected:

                You would have to either handle that case manually or use the dirty checksums that I did mention. The problem there is that you can test the data container of a node for being dirty, i.e. its parameters, and by that detect if a parameter has been modified, but not exactly which one of the parameters has been modified.

                Hmmm, bummer. Ya the whole goal was to only return parameters not at default value. I've made similar scripts for Houdini, and After Effects. Cinema4D was my last holdout. 🙂

                I'll take a look at the dirty checksums. If I can at least tell if something was changed, it's better than nothing I guess.

                Thanks again for the help.

                --
                David Torno
                https://fendrafx.com
                https://vimeo.com/davidtorno

                1 Reply Last reply Reply Quote 0
                • davidtornoD
                  davidtorno
                  last edited by davidtorno

                  Still far from being fully fleshed out, but could prove helpful to others in some form possibly. It's totally specific to my needs so I make no claims of it's longevity, stability, or usability for anyone else's needs. 🙂

                  The script prints to the console a tab delimited list of all project objects beginning with a selected object. It then loops downwards through all objects, object parameters, tags, and tag parameters gathering names & values. Choose top most object in your project to get all items in your project.

                  Here's the code I'm settling on for the moment. I did a few common parameter conversions from numerical value to it's string value seen in the UI.

                  Example 1: Toggle switches return "Unchecked" / "Checked" instead or 0 / 1
                  Example 2: Visible in Editor / Renderer return "On" / "Off" / "Default" instead of 0 / 1 / 2

                  Source code:

                  #--------------------------------------------------------
                  #    [IN DEVELOPEMENT AND NOT COMPLETED YET]
                  #    Project parameter names and values list script
                  #    9/2020 David Torno
                  #--------------------------------------------------------
                  
                  import c4d
                  from c4d import gui
                  
                  # Main function
                  def main():
                      o = op
                      if o is not None:
                          recurse(o)
                  
                  def getParamData(o):
                      try:
                          #--------------------------------------------------------
                          #    PARAMETER DATATYPE NUMBERS R20 (Likely to change with C4D versions)
                          #    1 = Group
                          #    8 = Button
                          #    12 = String
                          #    15 = Dropdown
                          #    133 = Object Link
                          #    1000481 = Color Gradient
                          #    1009290 = InExclude List
                          #    400006001 = Toggle
                          #--------------------------------------------------------
                          
                          #misc var
                          grpName = ""
                          #Rotation Order conversion
                          rotOrder = ["XYZ", "XZY", "YXZ", "YZX", "ZXY", "ZYX", "HPB"]
                          #Vis in editor/renderer conversion
                          visEditRender = ["On", "Off", "Default"]
                          #Toggle conversion
                          tog = ["Unchecked", "Checked"]
                          
                          #Process tag parameters
                          desc = o.GetDescription(c4d.DESCFLAGS_DESC_0)
                          for bc, paramid, groupid in desc:
                              #print str(paramid[0].id), str(paramid[0].dtype), str(paramid[0].creator)
                              
                              #-------------------------------------------------------
                              #Currently checking for default parameter value does not work
                              #-------------------------------------------------------
                              #Check if current value matches default value
                              #is_default = bc[c4d.DESC_DEFAULT] == o[paramid]
                              #print both values for visual comparison
                              #print "DEBUG:: " + str(bc[c4d.DESC_DEFAULT]) + " == " + str(o[paramid]) + " --> " + str(is_default)
                              #if is_default is not True:
                              #-------------------------------------------------------
                              
                              isgroup = paramid[0].dtype==c4d.DTYPE_GROUP
                              if isgroup:
                                  #handle blank names
                                  if bc[c4d.DESC_NAME] is not "":
                                      grpName = bc[c4d.DESC_NAME]
                                      print "\t" + grpName
                      
                              #check for param name and ident
                              if bc[c4d.DESC_NAME] and bc[c4d.DESC_IDENT]:
                                  try:
                                      #Adjust color float to 255 range
                                      vec = o[paramid]
                                      if "Color" in bc[c4d.DESC_NAME] and "Vector" in str(vec):
                                          vec = o[paramid]
                                          val = "Vector(" + str(vec[0]*255) + ", " + str(vec[1]*255) + ", " + str(vec[2]*255) + ")"
                                      else:
                                          blank = o[paramid]
                                          if blank == None:
                                              val = ""
                                          else:
                                              val = str(o[paramid])
                                              
                                      #Convert toggle value to name str
                                      if str(paramid[0].dtype) == "400006001":
                                          val = tog[o[paramid]]
                                      
                                      #Convert dropdown value to name str
                                      if str(paramid[0].dtype) == "15":
                                          if "Visible in " in bc[c4d.DESC_NAME]:
                                              val = visEditRender[o[paramid]]
                                          elif "Rotation Order" == bc[c4d.DESC_NAME]:
                                              val = rotOrder[o[paramid]]
                  
                                      
                                      #handle group name duplicates in results
                                      if bc[c4d.DESC_NAME] == grpName or paramid[0].dtype == 8:
                                          pass
                                      else:
                                          print "\t\t" + bc[c4d.DESC_NAME] + "\t" + val
                                          
                                          #--------------------------------------------------------------------
                                          #DEBUG print reveals parameter data type enumeration
                                          #--------------------------------------------------------------------
                                          #print "DTYPE:" + str(paramid[0].dtype) + "\t\t" + bc[c4d.DESC_NAME] + "\t" + val
                                  except:
                                      print "\t\t" + bc[c4d.DESC_NAME] + ": PARAMETER REQUIRES MANUAL EXTRACTION"
                                      
                                      #--------------------------------------------------------------------
                                      #DEBUG print reveals parameter data type enumeration
                                      #--------------------------------------------------------------------
                                      #print "DTYPE:" + str(paramid[0].dtype) + "\t\t" + bc[c4d.DESC_NAME] + ": PARAMETER REQUIRES MANUAL EXTRACTION"
                      except:
                          print "--UNKNOWN PARAMETER--"
                  
                  
                  def recurse(o):
                      obj = o
                      if obj is not None:
                          #Misc variables
                          tab = "\t"
                  
                          #Header
                          #print "Name\tParameter\tValue\tParam ID"
                  
                          #Print object name
                          on = obj.GetName()
                          print on
                  
                          #Get param name and value
                          getParamData(obj)
                  
                          #Get object tags
                          tags = obj.GetTags()
                          
                          #Loop tags
                          for t in tags:
                              tn = t.GetTypeName()
                              if tn is not "" or None:
                                  #Add spacer in output
                                  print ""
                  
                                  #Get tag name
                                  print t.GetTypeName()
                                  
                                  #Get param name and value
                                  getParamData(t)
                  
                  
                          #Check for child object
                          d = obj.GetDown()
                          if d is not None:
                              recurse(d)
                  
                          #Get next object
                          n = obj.GetNext()
                          if n is not None:
                              recurse(n)
                      else:
                          print "Error: Select one starting object first."
                  
                  # Execute main()
                  if __name__=='__main__':
                      main()
                  

                  Example output:

                  Right fill light
                  	Basic Properties
                  		Name	Right fill light
                  		Layer
                  		Visible in Editor	Off
                  		Visible in Renderer	Default
                  		Use Color	0
                  		Display Color	Vector(255.0, 255.0, 255.0)
                  		Enabled	Checked
                  		X-Ray	Unchecked
                  		Icon Color	Unchecked
                  	Coordinates
                  		Position	Vector(337.76, 0, -211.712)
                  		Scale	Vector(1, 1, 1)
                  		Rotation	Vector(1.326, 0, -1.571)
                  		Rotation Order	HPB
                  	Quaternion
                  		Quaternion Rotation	Unchecked
                  	Freeze Transformation
                  		Frozen Position	Vector(0, 0, 0)
                  		Frozen Scale	Vector(1, 1, 1)
                  		Frozen Rotation	Vector(0, 0, 0)
                  		Global Position	Vector(337.76, 0, -211.712)
                  		Global Rotation	Vector(1.326, 0, -1.571)
                  		Transformed Position	Vector(337.76, 0, -211.712)
                  		Transformed Scale	Vector(1, 1, 1)
                  		Transformed Rotation	Vector(1.326, 0, -1.571)
                  	Object Properties
                  	General
                  		Color	Vector(153.765, 216.8775, 229.5)
                  		Use Temperature	Unchecked
                  		Color Temperature	6500.0
                  		Intensity	0.65
                  		Type	8
                  		Shadow	2
                  		Visible Light	0
                  		No Illumination	Unchecked
                  		Show Illumination	Checked
                  		Ambient Illumination	Unchecked
                  		Show Visible Light	Checked
                  		Diffuse	Checked
                  		Show Clipping	Checked
                  		Specular	Checked
                  		Separate Pass	Unchecked
                  		GI Illumination	Checked
                  		Export to Compositing	Checked
                  	Details
                  		Use Inner Value	Checked
                  		Inner Angle	0.0
                  		Inner Radius	0.0
                  		Outer Angle	0.523598775598
                  		Outer Radius	600.0
                  		Aspect Ratio	1.0
                  		Contrast	0.0
                  		Shadow Caster	Unchecked
                  		Area Shape	1
                  		Object
                  		Size X	1200.0
                  		Size Y	1200.0
                  		Size Z	200.0
                  		Falloff Angle	3.14159265359
                  		Infinite Angle	0.00872664625997
                  		Samples	40
                  		Add Grain (Slow)	Unchecked
                  		Show in Render	Unchecked
                  		Show as Solid in Viewport	Unchecked
                  		Show in Specular	Checked
                  		Show in Reflection	Unchecked
                  		Visibility Multiplier	1.0
                  		Falloff	0
                  		Falloff Inner Radius	0.0
                  		Falloff Radius / Decay	500.0
                  		Colored Edge Falloff	Unchecked
                  		Z Direction Only	Unchecked
                  		Use Gradient	Unchecked
                  		Color	<c4d.Gradient object at 0x000000D7967B9570>
                  		Use Near Clipping	Unchecked
                  		Near Clipping from	0.0
                  		Near Clipping to	10.0
                  		Use Far Clipping	Unchecked
                  		Far Clipping from	90.0
                  		Far Clipping to	100.0
                  

                  --
                  David Torno
                  https://fendrafx.com
                  https://vimeo.com/davidtorno

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

                    Hi,

                    thanks for sharing, it is always nice if someone does that. I haven't red all your code and also did not run it, but I think the following points should be made:

                    # You should not type test like that, because it is bound to go wrong at
                    # some point.
                    if "Vector" in str(vec):
                        pass
                    
                    # Either do this, which will test for 'vec' being exactly of type c4d.Vector
                    # or c4d.Vector4d:
                    if type(vec) in (c4d.Vector, c4d.Vector4d):
                        pass
                    # Or do this, which will test if 'vec' is also of type c4d.Vector or 
                    # c4d.Vector4d, i.e. is respecting polymorphism. Which does not make much 
                    # sense here, since both Vector and Vector4d are atomic types. 
                    # This however is the recommended approach to type tesing in Python, because 
                    # usually you want and need to respect polymorphism.
                    if isinstance(vector, (c4d.Vector, c4d.Vector4d)):
                        pass
                                
                                                        
                    # Also your string handling is more complicated than it needs to be (you should
                    # also stick to PEP8, specifically the line length here):
                    val = ("Vector(" + str(vec[0]*255) + ", " + str(vec[1]*255) + ", " + str(vec[2]*255) + ")")
                    
                    # Use Pythons string formating instead, the values will be put in consecutive
                    # order into the curly brackets. There is a whole string formating mini
                    # language, which will allow you to do all sorts of stuff, like aligning text,
                    # converting between number formats and more. Check the Python docs on the
                    # subject if you want to know more.
                    print "Vector(r:{}, g:{}, b:{})".format(int(vec.x * 255), 
                                                            int(vec.y * 255), 
                                                            int(vec.z * 255))
                    

                    Cheers,
                    zipit

                    MAXON SDK Specialist
                    developers.maxon.net

                    davidtornoD 1 Reply Last reply Reply Quote 0
                    • davidtornoD
                      davidtorno @ferdinand
                      last edited by

                      @zipit Thanks! All great points. I was trying to check for instance of object initially, but was failing to find the correct syntax in this case. I got impatient and started hacking together solutions that still function even if not optimized/ideal.

                      I am so use to Adobe ExtendScript which is JavaScript based and Houdini VEX which is more python/JavaScript like, and their hscript which is Python based. So C4D python syntax can be just close enough to drive me nuts when I don’t find the correct answer. I’ve struggled with C4D python for a long time too. The docs really get confusing fast (for me at least)

                      One thing that helped me with Adobe ExtendScript is the object model graph they have in the docs. It showed the object flow hierarchy and each class name. So it was super easy to find every method and attribute associated with each. Almost linear in nature. I think also what help most was the object naming though, it matched more with the front end naming, so it was easy to connect the dots. That’s mostly where I struggle in the C4D docs, and confusion sets in fast for me. Like BaseContainer, Atom, Description, etc... I see the App > Object Panel > Object > Parameter > Value (as a very basic example)

                      Granted I never took a traditional route for learning coding, and am self taught. 🙂

                      Thanks again for the tips you keep mentioning. I really appreciate it.

                      --
                      David Torno
                      https://fendrafx.com
                      https://vimeo.com/davidtorno

                      1 Reply Last reply Reply Quote 0
                      • M
                        m_adam
                        last edited by

                        Hi sorry to come late to the party, so to answers your initial questions:

                        • DESC_CHANGED is only defined for intcustomgui (also used for float value) when the value is changed, so it's not as helpful as one may think.
                        • DESC_DEFAULT, as said by zipit it's to the object, to properly set the default value, otherwise, you will get the default DataType value which may not be the default value of an object.

                        So I took another way to extract what changed and what not changed, by simply creating a new object and check the changed value. This also supports tag additions.

                        import c4d
                        
                        class Parameter(object):
                            # Represent a parameter
                            def __init__(self, paramIdent, paramName, paramValue):
                                self.paramIdent = paramIdent
                                self.paramName = paramName
                                self.paramValue = paramValue
                        
                            def __str__(self):
                                return '"{1}", c4d.{0} {2}'.format(self.paramIdent, self.paramName, self.paramValue)
                            
                            def __repr__(self):
                                return str(self)
                                
                        
                        def GetChangedParameter(bl2D):
                            # Retrieve the changed parameter, by creating the BaseList2D and comparing the description and value
                            
                            # Create an object with default value
                            cls = bl2D.__class__
                            blTypeId = bl2D.GetType()
                            
                            newBl2D = cls(blTypeId)
                            newBl2D.Message(c4d.MSG_MENUPREPARE, None)
                        
                            changedParams = []  # Will contains all the changed Parameter
                            unwantedIds = [c4d.ID_BASELIST_ICON_COLOR, 
                              c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST]  # List of excluded parameter because they are always different
                            
                            # Iterate teh description of the existing obj
                            description = bl2D.GetDescription(c4d.DESCFLAGS_DESC_0)
                            for bc, paramId, groupId in description:
                                # if we dont want to deal with this parameter continue the loop
                                if paramId[0].id in unwantedIds:
                                    continue
                                
                                # Ignore group
                                if paramId[0].dtype == c4d.DTYPE_GROUP:
                                    continue
                                
                                # If a parameter is not within the newly created, something went wrong
                                if not description.CheckDescID(paramId, [newBl2D]):
                                    raise ValueError("{0} not found in the created instance".format(paramId))
                                
                                # Try excecute, because some Datatype are not present in Python
                                # here we retrieve the value of the parameter in the scene Bl2D and the newly created
                                try:
                                    bl2DValue = bl2D[paramId]
                                    newBl2DValue = newBl2D[paramId]
                                    
                                    # If they are not equal, append a new parameter in the changedParams list
                                    if bl2DValue != newBl2DValue:
                                        newParam = Parameter(bc[c4d.DESC_IDENT], bc[c4d.DESC_NAME], bl2DValue)
                                        changedParams.append(newParam)
                                except:
                                    pass
                                
                            return changedParams
                                
                        
                        def HierarchyIterator(bl2D):
                            """
                            A Generator to iterate over the Hierarchy
                            :param bl2D: The starting object of the generator (will be the first result)
                            :return: All objects under and next of the `bl2D`, so any BaseList2D
                            """
                            while bl2D:
                                yield bl2D
                                for opChild in HierarchyIterator(bl2D.GetDown()):
                                    yield opChild
                                bl2D = bl2D.GetNext()
                                
                        def GetAddedTags(obj, excludedTags):
                            # Retrieve added tags by creating the object and look which tags are added or not
                            if not isinstance(obj, c4d.BaseObject):
                                return
                            
                            # Create a new obj
                            objTypeId = obj.GetType()
                            newObj = c4d.BaseObject(objTypeId)
                            
                            # Function which return a dict with key as Tag Type and entry are a list of tags, {tagType: [c4d.BaseTag]}
                            def _GetTags(obj, excludedTags):
                                tags = {}
                                for tag in obj.GetTags():
                                    tagType = tag.GetType()
                                    if tagType in excludedTags:
                                        continue
                                    
                                    if tagType in tags:
                                        tags[tagType].append(tag)
                                        
                                    else:
                                        tags[tagType] = [tag]
                                        
                                return tags
                            
                            # Retrieve Tags information
                            tags = _GetTags(obj, excludedTags)
                            newTags = _GetTags(newObj, excludedTags)
                            
                            missingTags = []  # will be filled with missing tags
                            
                            for tagId in tags:
                                # A tag type is not present in the new one add it
                                if tagId not in newTags:
                                    for t in tags[tagId]:
                                        missingTags.append(t)
                                        
                                    continue
                                        
                                # obj present in the scene have more tag than the defualt one
                                if len(tags[tagId]) > len(newTags[tagId]):
                                    missingTags.extend(newTags[len(tags[tagId]):])
                                    
                                        
                            return missingTags
                                
                            
                        
                        
                        # Main function
                        def main():
                            ignoredObjs = []
                            ignoredTags = [c4d.Tphong]
                            for obj in HierarchyIterator(doc.GetFirstObject()):
                                if obj.GetType() in ignoredObjs:
                                    continue
                                
                                changedParams = GetChangedParameter(obj)
                                addedTags = GetAddedTags(obj, ignoredTags)
                                
                                # Print obj Name
                                if changedParams or addedTags:
                                    print(obj.GetName())
                                    
                                # Print obj changed parameter
                                for param in changedParams:
                                    print("{0}{1}".format(" "*2, param))
                                    
                                # Print missing Tag
                                for tag in addedTags:
                                    print("{0}Missing Tag: {1}".format(" "*2, tag.GetName()))
                                    
                                    # Print modified value for phong
                                    for param in GetChangedParameter(tag):
                                        print("{0}{1}".format(" "*4, param))
                        
                        # Execute main()
                        if __name__=='__main__':
                            main()
                        

                        Regarding the Python documentation, we are of course looking to improve it, so if you have any advice, please feel free to give us. Just a side note each class you can see the parent class and children classes, so you can quickly find all the methods of a class. Here an example for the BaseList2D class f976e1a7-7990-478b-a11e-041ce6339f0d-image.png

                        Cheers,
                        Maxime.

                        MAXON SDK Specialist

                        Development Blog, MAXON Registered Developer

                        davidtornoD 1 Reply Last reply Reply Quote 0
                        • davidtornoD
                          davidtorno @m_adam
                          last edited by

                          @m_adam Wow, thanks Maxime for the explanation and code. I was not expecting that amount of effort.

                          I ran the code and encountered two errors so far. I believe one may be App version related possibly, and the other I am unsure.

                          So first the section in your GetChangedParameter function

                           # List of excluded parameter because they are always different
                          unwantedIds = [c4d.ID_BASELIST_ICON_COLOR,
                                                   c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST]
                          

                          I get this attribute error

                          AttributeError: 'module' object has no attribute 'ID_BASELIST_ICON_COLOR'
                          

                          I am assuming this may be an ID added after R20. I am still using R20 for context. Once I remove it from the list the rest of the code will execute.

                          The second error I get is when encountering any polygon object. For example you can throw down any primitive shape and make it editable to test this.

                          When a polygon object is encounter this type error is thrown:

                          TypeError: Required argument 'vcnt' (pos 2) not found
                          

                          My guess is that since it's a polygon object (which could be a native primitive made editable or a custom geometry imported by user) C4D has no direct reference to recreate the object in a default state?

                          So far those are the only two issues I have come across.

                          --
                          David Torno
                          https://fendrafx.com
                          https://vimeo.com/davidtorno

                          1 Reply Last reply Reply Quote 0
                          • M
                            m_adam
                            last edited by

                            Hi @davidtorno sorry I overlooked the version in the post tag.
                            You are right ID_BASELIST_ICON_COLOR was added in R21. So you could remove it.

                            Regarding the second issue, you are also right, a PolygonObject/SplineObject have no history at all, so you can't trace them, even modeling operation done on them can't be retrieved at least by the way I propose.
                            You could do a SceneHook(only available in C++) to monitor the change made by a tool by listening to EVMSG_CHANGE (triggered when a EventAdd is processed) figured out if a polygon object changed (by iterating over each scene object, and checking their dirty bits), then retrieve the current tool and parameters, but as you can see it's a pretty complex task.

                            So in your case I would suggest to simply ignore edited object (so PolygonObject and SplineObject) by editing ignoredObjs variable like so

                            ignoredObjs = [c4d.Opolygon, c4d.Opoint, c4d.Ospline, c4d.Oline]
                            

                            Cheers,
                            Maxime.

                            MAXON SDK Specialist

                            Development Blog, MAXON Registered Developer

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

                              Hello @davidtorno,

                              without any further questions or replies, we will consider this topic to be solved by Monday and flag it accordingly.

                              Thank you for your understanding,
                              Ferdinand

                              MAXON SDK Specialist
                              developers.maxon.net

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