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

    How to compare a port value has been changed?

    Cinema 4D SDK
    windows python 2024
    2
    8
    1.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.
    • DunhouD
      Dunhou
      last edited by

      Hi community,

      I would like to know if there is a way to obtain the default value of a port(maxon.GraphNode), then catch which port in the graph is changed. I'm not sure if I missed something, but I didn't find any relevant methods in the document.

      Any suggestions are welcomed!

      Cheers~
      DunHou

      https://boghma.com
      https://github.com/DunHouGo

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

        Hi @Dunhou there is the method GraphModelInterface.GetModificationStamp which return you a TimeStamp of the last change (aka the last committed transaction).
        Then with this stamp you can use GraphModelInterface.GetModificationsSince that will return you which GraphNode (so port since ports are node) are modified.
        With that's said currently GetModificationsSince only accept list and not directly a callback, if you want to use a callback you need to use _GetModificationsSinceComplex.

        Find bellow a code example

        import c4d
        import maxon
        
        
        def main():
            # Retrieve the selected baseMaterial
            mat = c4d.BaseMaterial(c4d.Mmaterial)
            if mat is None:
                raise ValueError("Cannot create a BaseMaterial")
        
            # Retrieve the reference of the material as a node Material.
            nodeMaterial = mat.GetNodeMaterialReference()
            if nodeMaterial is None:
                raise ValueError("Cannot retrieve nodeMaterial reference")
        
            # Define the ID of standard material node space and print a warning when the active node space
            # is not the the standard material node space.
            nodeSpaceId = maxon.Id("net.maxon.nodespace.standard")
            if nodeSpaceId != c4d.GetActiveNodeSpaceId():
                print (f"Warning: Active node space is not: {nodeSpaceId}")
        
            # Add a graph for the standard node space.
            addedGraph = nodeMaterial.CreateDefaultGraph(nodeSpaceId)
            if addedGraph is None:
                raise ValueError("Cannot add a graph node for this node space")
        
            # Retrieve the Nimbus reference for a specific node space from which we 
            # will retrieve the graph. One could also use 'addedGraph' defined above.
            nimbusRef = mat.GetNimbusRef(nodeSpaceId)
            if nimbusRef is None:
                raise ValueError("Cannot retrieve the nimbus ref for that node space")
        
            # Retrieve the graph corresponding to that node space.
            graph = nimbusRef.GetGraph()
            if graph is None:
                raise ValueError("Cannot retrieve the graph of this nimbus ref")
        
            # Retrieve the end node of this graph
            endNodePath = nimbusRef.GetPath(maxon.NIMBUS_PATH.MATERIALENDNODE)
            endNode = graph.GetNode(endNodePath)
            if endNode is None:
                raise ValueError("Cannot retrieve the end-node of this graph")
        
            # Retrieve the predecessors. Function have been moved in R26.
            predecessor = list()
            maxon.GraphModelHelper.GetDirectPredecessors(endNode, maxon.NODE_KIND.NODE,  predecessor)
            bsdfNode = predecessor[0]
            if bsdfNode is None:
                raise ValueError("Cannot retrieve the node connected to end-node")
        
            # Retrieve the outputs list of the BDSF node
            if bsdfNode is None and not bsdfNode.IsValid() :
                raise ValueError("Cannot retrieve the inputs list of the bsdfNode node")
            bsdfNodeInputs = bsdfNode.GetInputs()
            colordePort = bsdfNodeInputs.FindChild("color")
            if colordePort is None:
                return
        
            stamp = graph.GetModificationStamp()
        
            with graph.BeginTransaction() as transaction:
                # Define the value of the Color's port.
                colordePort.SetPortValue(maxon.ColorA(1, 0, 0, 1))
                transaction.Commit()
            
            # Get the change via a list
            modifieds = []
            graph.GetModificationsSince(stamp, modifieds, True)
            
            for node, flag in modifieds:
                print(flag, node)
            
            # Get the change via a callback, due to a bug that is going to be fixed, you can call GetModificationsSince with a callback
            def callback(node: maxon.GraphNode, flag: maxon.GraphModelInterface.MODIFIED):
                print(flag, node)
                return True
            
            graph._GetModificationsSinceComplex(stamp, callback, True)
        
            doc.InsertMaterial(mat)
            c4d.EventAdd()
        
        
        if __name__ == "__main__":
            main()
        

        Sadly there is a missing bit in Python, since in C++ we have the concept of obverser, which allow for a lambda to be executed when a particular event occurs. Sadly observer are not yet implemented in Python, it's on the to-do list but for the moment it's not there. And in C++ there is the observer ObservableTransactionCommitted, which let you react to a graph transaction commit. So for the moment your best bet is to monitor for EVMSG_CHANGE or use a Timer to monitor the timestamp and check if it changed.

        Cheers,
        Maxime.

        MAXON SDK Specialist

        Development Blog, MAXON Registered Developer

        DunhouD 1 Reply Last reply Reply Quote 0
        • DunhouD
          Dunhou @m_adam
          last edited by

          Hey @m_adam ,

          Thanks for that quick answer and a sweet codes for GraphModelInterface.GetModificationStamp I took a look at this, but these might be some details I still need.

          For example, I get a material from others. I want to check the material:

          • oh, He/She modified the ior value to 2. That is a change applied to result.
          • also He/She changed roughness 0 -> 0.2 -> 0. It has been modified but that is a default value, I didn't want to catch this.

          Can we get the this stored value here, aka the default value we create the new material?

          Cheers~
          DunHou

          5bc7258b-fbc1-4da3-8dc3-d89c48562b64-image.png

          https://boghma.com
          https://github.com/DunHouGo

          1 Reply Last reply Reply Quote 0
          • DunhouD
            Dunhou
            last edited by

            Is any luck here 😊

            https://boghma.com
            https://github.com/DunHouGo

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

              Hi @Dunhou the default value can be retrieved via:
              port.GetValue(maxon.DESCRIPTION.DATA.BASE.DEFAULTVALUE)

              However note that sometime the value can be None, in this case its the default value of the datatype that is used.
              Cheers,
              Maxime.

              MAXON SDK Specialist

              Development Blog, MAXON Registered Developer

              DunhouD 1 Reply Last reply Reply Quote 0
              • DunhouD
                Dunhou @m_adam
                last edited by

                Hey @m_adam ,

                I think this is equal to port.GetValue('value'), and it will get the current value for the node but not the default value.

                if I change the roughness to 0.5, they both return 0.5 but bot 0 for default.

                Cheers~
                DunHou

                https://boghma.com
                https://github.com/DunHouGo

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

                  Hi sorry I forget about that, so SetPortValue set the DESCRIPTION::DATA::BASE::DEFAULTVALUE at the port level. Defining a new default value for this port for the current graph. Values are coming from DataBaseDescription, then Graph. So if the graph does not define a new default value, then the value from the DataBase is used. Its a simplified explanation you can have multiple layers of "data" holder in between, but you get the idea.

                  If you want to retrieve the "user" default value you either have to go via PortDescription (which are not supported in Python) to retrieve the value stored in the description or use the GraphModelInterface.GetBaseValues which is also not supported in Python for the moment. I will add the later one in the next version of Cinema 4D.

                  With that's said you can add yourself the GetBaseValues like so, by adding that at the top of your file:

                  
                  @maxon.MAXON_INTERFACE(maxon.MAXON_REFERENCE_NORMAL, "net.maxon.graph.interface.graphmodel")
                  class GraphModelInterface(maxon.ObserverObjectInterface):
                  
                      @maxon.MAXON_METHOD('net.maxon.graph.interface.graphmodel.GetBaseValues')
                      def GetBaseValues(self):
                          pass
                  
                  def GetBaseValuesGN(self, attr, expectedType, receiver):
                      return self._GetGraph().GetBaseValues(self, attr, expectedType, receiver)
                  
                  maxon.GraphModelInterface.GetBaseValues = GraphModelInterface.GetBaseValues
                  maxon.GraphNode.GetBaseValues = GetBaseValuesGN
                  

                  Then find bellow a function to retrieve the DefaultValue of a GraphNode.

                  
                      def GetDefaultValue(node: maxon.GraphNode):
                          defaultValue = [None]
                                  
                          def GetDefaultValue(data, nesting):
                              if nesting >= 0:
                                  defaultValue[0] = data
                                  return False
                              return True
                                    
                          node.GetBaseValues(maxon.DESCRIPTION.DATA.BASE.DEFAULTVALUE, None, GetDefaultValue)
                          
                          if defaultValue[0] is None:
                              # Get the default value from the DataType default value.
                              # We retrieve the DataType from the current value type, ideally you should go through the database description.
                              # But database description are not available in Python for the moment.
                              currentValue = node.GetPortValue()
                              if currentValue is not None:
                                  dt = currentValue.GetType()
                                  defaultValue[0] = maxon.Data(dt.Create())
                              
                          return defaultValue[0]
                  

                  So with the code example I posted above, this give:

                  import c4d
                  import maxon
                  
                  @maxon.MAXON_INTERFACE(maxon.MAXON_REFERENCE_NORMAL, "net.maxon.graph.interface.graphmodel")
                  class GraphModelInterface(maxon.ObserverObjectInterface):
                  
                      @maxon.MAXON_METHOD('net.maxon.graph.interface.graphmodel.GetBaseValues')
                      def GetBaseValues(self):
                          pass
                  
                  def GetBaseValuesGN(self, attr, expectedType, receiver):
                      return self._GetGraph().GetBaseValues(self, attr, expectedType, receiver)
                  
                  maxon.GraphModelInterface.GetBaseValues = GraphModelInterface.GetBaseValues
                  maxon.GraphNode.GetBaseValues = GetBaseValuesGN
                  
                  def main():
                      # Retrieve the selected baseMaterial
                      mat = c4d.BaseMaterial(c4d.Mmaterial)
                      if mat is None:
                          raise ValueError("Cannot create a BaseMaterial")
                  
                      # Retrieve the reference of the material as a node Material.
                      nodeMaterial = mat.GetNodeMaterialReference()
                      if nodeMaterial is None:
                          raise ValueError("Cannot retrieve nodeMaterial reference")
                  
                      # Define the ID of standard material node space and print a warning when the active node space
                      # is not the the standard material node space.
                      nodeSpaceId = maxon.Id("net.maxon.nodespace.standard")
                      if nodeSpaceId != c4d.GetActiveNodeSpaceId():
                          print (f"Warning: Active node space is not: {nodeSpaceId}")
                  
                      # Add a graph for the standard node space.
                      addedGraph = nodeMaterial.CreateDefaultGraph(nodeSpaceId)
                      if addedGraph is None:
                          raise ValueError("Cannot add a graph node for this node space")
                  
                      # Retrieve the Nimbus reference for a specific node space from which we
                      # will retrieve the graph. One could also use 'addedGraph' defined above.
                      nimbusRef = mat.GetNimbusRef(nodeSpaceId)
                      if nimbusRef is None:
                          raise ValueError("Cannot retrieve the nimbus ref for that node space")
                  
                      # Retrieve the graph corresponding to that node space.
                      graph = nimbusRef.GetGraph()
                      if graph is None:
                          raise ValueError("Cannot retrieve the graph of this nimbus ref")
                  
                      # Retrieve the end node of this graph
                      endNodePath = nimbusRef.GetPath(maxon.NIMBUS_PATH.MATERIALENDNODE)
                      endNode = graph.GetNode(endNodePath)
                      if endNode is None:
                          raise ValueError("Cannot retrieve the end-node of this graph")
                  
                      # Retrieve the predecessors. Function have been moved in R26.
                      predecessor = list()
                      maxon.GraphModelHelper.GetDirectPredecessors(endNode, maxon.NODE_KIND.NODE,  predecessor)
                      bsdfNode = predecessor[0]
                      if bsdfNode is None:
                          raise ValueError("Cannot retrieve the node connected to end-node")
                  
                      # Retrieve the outputs list of the BDSF node
                      if bsdfNode is None and not bsdfNode.IsValid() :
                          raise ValueError("Cannot retrieve the inputs list of the bsdfNode node")
                      bsdfNodeInputs = bsdfNode.GetInputs()
                      colordePort = bsdfNodeInputs.FindChild("color")
                      if colordePort is None:
                          return
                  
                      stamp = graph.GetModificationStamp()
                  
                      with graph.BeginTransaction() as transaction:
                          # Define the value of the Color's port.
                          colordePort.SetPortValue(maxon.ColorA(1, 1, 0, 1))
                          transaction.Commit()
                  
                      def GetDefaultValue(node: maxon.GraphNode):
                          defaultValue = [None]
                                  
                          def GetDefaultValue(data, nesting):
                              if nesting >= 0:
                                  defaultValue[0] = data
                                  return False
                              return True
                                    
                          node.GetBaseValues(maxon.DESCRIPTION.DATA.BASE.DEFAULTVALUE, None, GetDefaultValue)
                          
                          if defaultValue[0] is None:
                              # Get the default value from the DataType default value.
                              # We retrieve the DataType from the current value type, ideally you should go through the database description.
                              # But database description are not available in Python for the moment.
                              currentValue = node.GetPortValue()
                              if currentValue is not None:
                                  dt = currentValue.GetType()
                                  defaultValue[0] = maxon.Data(dt.Create())
                              
                          return defaultValue[0]
                  
                      # Get the change via a callback, due to a bug that is going to be fixed, you can't call GetModificationsSince with a callback
                      def callback(node: maxon.GraphNode, flag: maxon.GraphModelInterface.MODIFIED):
                          if flag == maxon.GraphModelInterface.MODIFIED.DATA_ATTRIBUTE_MASK:
                              print(flag, node, GetDefaultValue(node))
                              
                          return True
                  
                      graph._GetModificationsSinceComplex(stamp, callback, True)
                  
                      doc.InsertMaterial(mat)
                      c4d.EventAdd()
                  
                  
                  if __name__ == "__main__":
                      main()
                  

                  Cheers,
                  Maxime.

                  MAXON SDK Specialist

                  Development Blog, MAXON Registered Developer

                  DunhouD 1 Reply Last reply Reply Quote 0
                  • DunhouD
                    Dunhou @m_adam
                    last edited by

                    Wow @m_adam , thanks for that, I'll check this after work!

                    https://boghma.com
                    https://github.com/DunHouGo

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