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.