you can use our contact from
https://developers.maxon.net/forum/contact
or send us an email at [email protected]
you can use our contact from
https://developers.maxon.net/forum/contact
or send us an email at [email protected]
Hey Daniel,
We are not allowed to talk about our roadmap.
I still do not understand how you are using those data in your workflow. Maybe you want the Meta-Data to be accessible with python, Xpresso, something else. Or you just want to import an fbx in c4d, do something and export it again without loosing those data.
Cinema 4D is reading everything he can handle, other information is ignored, so yes, meta-data are lost.
If you cannot or do not want to talk about your workflow in public maybe we can use our mail box if you feel more confortable?
Cheers,
Manuel
Hi,
I tested the following code and while i got some error on a Redshift material in 2023.0 and 2023.1 i have no error anymore in 2023.2.1
Standard renderer seems to always works. So this seems to not be an issue with the function FindNodesByName itself.
There were lots of improvement in the node API and i can just guess what have been fixed.
Can you still reproduce the issue in 2023.2.1 ?
import c4d
import maxon
import time
def main():
# Retrieve the selected BaseMaterial
mat = doc.GetActiveMaterial()
if mat is None:
raise ValueError("There is no selected BaseMaterial")
# Retrieve the reference of the material as a node material.
nodeMaterial = mat.GetNodeMaterialReference()
if nodeMaterial is None:
raise ValueError("Cannot retrieve node material reference")
# Retrieve the current node space Id
nodespaceId = c4d.GetActiveNodeSpaceId()
# Retrieve the Nimbus reference for a specific node space
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")
settings: maxon.DataDictionaryInterface = maxon.DataDictionary()
settings.Set(maxon.nodes.UndoMode, maxon.nodes.UNDO_MODE.START)
for loopId in range(0, 100):
with graph.BeginTransaction(settings) as transaction:
input_node = []
scale_node = graph.AddChild("", "net.maxon.node.type", maxon.DataDictionary())
scale_node.SetValue(maxon.NODE.BASE.NAME, maxon.String("scale_node"))
maxon.GraphModelHelper.FindNodesByName(graph, "scale_node", maxon.NODE_KIND.ALL_MASK, maxon.PORT_DIR.INPUT, True, input_node)
print (input_node)
if len(input_node) < 1:
print("cannot find the node")
transaction.Commit()
with graph.BeginTransaction(settings) as transaction:
input_node = []
maxon.GraphModelHelper.FindNodesByName(graph, "scale_node", maxon.NODE_KIND.ALL_MASK, maxon.PORT_DIR.INPUT, True, input_node)
for graphnode in input_node:
graphnode.Remove()
transaction.Commit()
# Pushes an update event to Cinema 4D
c4d.EventAdd()
if __name__ == "__main__":
main()
Cheers,
Manuel
You can use SetParameter or change the value directly in the BaseContainer. Using SetParameter is a better option if you want to move the data anywhere else outside the BaseContainer. If you do so, you will have to override the NodeData.SetDParameter function to handle your data properly. (note the difference, there is a D in that function name)
Calling SetParameter will work on both cases, storing your data in a BaseContainer or in your own way.
It is always a clever idea to initialise your data.
I would rather clean the BaseContainer when you press the "delete" button. That will avoid having a BaseContainer getting bigger and bigger, especially if you copy paste your generator from document to document.
Cheers,
Manuel
@ThomasB said in Problem Adding description parameter by clicking button and access this:
You told me something about cleaning the BaseContainer?
2023-05-11 01-11-45.mp4
hi, that is exactly why i was talking about cleaning the BaseContainer, or to initialise correctly the values. It is even worse if you mix datatype.
I was thinking of the morph tag and the way he does add or remove morph target using the same IDs.
And of course, removing data that you are not using anymore is a good idea.
Cheers,
Manuel
Hi,
sorry, we have a company meeting next week, i will not be able to check again this issue until 15 of May.
many things were fixed in 2023.2.
Cheers,
Manuel
@yesbird said in Accessing Sweep's spline data from C4DImportExport.cpp:
Could you tell me please, if you have plans of it's implementation ?
If not, I could implement it myself, having access to Cineware sources.
HI,
I'm sorry but we cannot share this kind of information or the source code. The function is implemented in the regular SDK. i am sure that there were technical difficulties to make it available in Cineware.
Cheers,
Manuel
@Aaron said in Save Project with Assets not updating the maxon::Url of node textures in R26 and 2023:
I am Kirill from CentiLeo project. I have written a question here because it seems there is more life on the public forum and the nodes api is now public.
By the way i hope you have access to our beta forum. There is a thread from 2021 in our gallery, a small test of Centileo. Now I remember the time you asked questions about our node API, in our old beta forum, answered by Sebastian and Ole. If you do not have access to our new forum or are not a registered developer, you can join us at sdk_support at maxon.net so we can talk about it.
I could test the pluging and, first, amazing job, now i understand how much effort you put on that project and why it looks so great. Sorry for not remembering immediately, it is sometime hard to follow everybody's project.
About the issue, i tested it with 2023.1.3 and could reproduce the issue but not with 2023.2. In 2023.2, it is working as expected. Our standard/physical nodespace is also working with 2023.3.1 so it is not a global bug.
The Project Asset Inspector have been improved in 2023.2 and texture renaming should work now.
In your implementation of maxon::nodes::NodeSystemClassInterface
SupportsImpl, you can define what node are supported in your nodespace.
const maxon::Id& id = templ.GetId();
const maxon::String idString = id.ToString();
if (idString.StartsWith("net.maxon"_s))
return true;
After that you should be able to instantiate any of our nodes inside your graph and test if ImportData url is renamed or not. It is just to see if it is a node issue (that would be surprising now) or an issue with your nodespace.
On my side, i will compile 2023.1.3 today and have a look, from what i see in the code there is no reason to not work. I will also have to ask our developers if they have an idea.
Cheers,
Manuel
hi,
sorry for the delay.
thanks a lot for your help here @Deyan i do not think @Aaron is implementing its own material.
After looking in our code, i can confirm that the node system will received MSG_MULTI_CLEARSUGGESTEDFOLDER
and MSG_RENAMETEXTURES
and for both doing the same thing.
It looks into all the ports inside the node system and check if the DataType of the port is Url
. Whatever the node is.
Now i am wondering if you set the datatype correctly of you texture node and did not used a string instead?
What you can try is to include the "Import Data' node inside your material setup and see if this node url will be renamed when you save the project with assets. That way we will know if the problem is your node or not.
you can no longer open the resource editor with a right click. g_developerNodeEditorFunctions
has been removed.
Cheers,
Manuel
hi,
well the main problem is not where but when to change that MoData. Specially in python i see no solution.
With c++ you could find a hacky way of doing it but i did not tried.
One way of doing it would be to create a setup inside the message function of the tag (so you are on the main thread). Creating a formula effector, hide it inside the object manager and use the formula to hide the object you want. something like id!=2&id!=3
will work. Of course, you need to include that effector to the cloner's effector list. I would not call that a "good solution", but it will work.
Cheers,
Manuel.
hi,
this should not be different than any node inside c4d (including scene node).
Tracks are stored inside a Baselist2D object created and linked to any node.
You must get the path of the node and retrieve the object using GetBaseListForNode.
Once you got the object, it will work as expected with GetCTracks
for example. I included in the example below how to get directly the descID of a port and the corresponding track.
from typing import Optional
import c4d
import maxon
# for this example we are creating keys for the color and the intensity parameter of the BSDF node inside
# a standard/physical material
doc: c4d.documents.BaseDocument # The active document
op: Optional[c4d.BaseObject] # The active object, None if unselected
def main() -> None:
# Get the active material
mat = doc.GetActiveMaterial()
if mat is None:
raise ValueError("There is no selected BaseMaterial")
# Retrieve the reference of the material as a node Material.
nodeMaterial = mat.GetNodeMaterialReference()
if nodeMaterial is None:
raise ValueError("can't retrieve nodeMaterial reference")
# Retrieve the current node space Id
nodespaceId = c4d.GetActiveNodeSpaceId()
# Get the nimbusref, this is useful to retrieve the DescID of a parameter.
nimbusRef = nodeMaterial.GetNimbusRef(nodespaceId)
graph = nodeMaterial.GetGraph(nodespaceId)
if graph is None:
raise ValueError("can't retrieve the graph of this nimbus ref")
root = graph.GetRoot()
# Delegate function that will retrieve the tracks for a port.
def PrintInfo(port):
# Get the path of the node. We are looking for port that must have "color" somewhere in their path.
portPath = port.GetPath()
if "color" in portPath:
# Retrieve information of the True node this port is part of
# For each GraphNode (node and port) a BaseList2D is created.
parentNode = port.GetAncestor(maxon.NODE_KIND.NODE)
parentNodePath = parentNode.GetPath()
# Retrieve the descID corresponding to this port.
portDescID = nimbusRef.GetDescID(portPath)
# Retrieve the BaseList2D object corresponding to the True Node and NOT the port.
obj = nodeMaterial.GetBaseListForNode(nodespaceId, parentNodePath)
if obj is None:
return False
# As this parameter is a color, the parameter will have one track per sub channel.
# The last level of the descID must be added.
portDescID.PushId(c4d.DescLevel(c4d.COLORA_R))
# Find the right track with this DescID on the True Node Baselist2D object
track = obj.FindCTrack(portDescID)
print (track)
return True
nodesList = []
root.GetChildren(nodesList, maxon.NODE_KIND.NODE)
for node in nodesList:
# Another way of printing all the track a node have. Retrieve the object corresponding to the node.
# Once the right object is founded, track works the same as any regular c4d object.
# nodePath = node.GetPath()
# obj = nodeMaterial.GetBaseListForNode(nodespaceId, nodePath)
# for track in obj.GetCTracks():
# print (track)
node.GetInputs().GetChildren(PrintInfo, maxon.NODE_KIND.PORT_MASK)
"""
def state():
# Defines the state of the command in a menu. Similar to CommandData.GetState.
return c4d.CMD_ENABLED
"""
if __name__ == '__main__':
main()
Cheers,
Manuel
Hi,
sorry i am a bit lost with your questions and i need a bit of clarification on some aspect.
If i understand correctly, you are implementing your own node space with your own texture node and the url is note updated when you use "save project with asset' i need more investigation time on that sorry.
About the Asset Inspector, if i am correct, this was a bug but fixed for a coming release. What i do not understand is that you are saying that the bitmap related code is the same but working in r21-25. If i am correct, the Node Api was not available for public with r21. So i am confused here, what code are you talking about?
g_developerNodeEditorFunctions
have been removed and nothing is replacing it. The Edit Resource...
command will be available only for groups. It will also be displayed when you right click on the node editor itself (the root) but will do nothing as the root is not supposed to be edited.
Cheers,
Manuel
hi,
I got the feeling it will not work but i need to investigate a lot more. Either you are modifying the MoData too early, and the cloner will erase/initialise them after you modified them, or you are modifying them after the cloner have generated the clones.
That is why we have the effectors to modify the Modata before the cloner have generated the clones.
Is there any reason you want to use a tag instead of an effector?
Cheers,
Manuel
Hi,
sorry, the display of this tab is hardcoded an only available for a couple of builtin objects.
What you can do is have a Matrix object in object mode that point to your generator, the rs tag being applied to the matrix object.
You can also create that setup inside your generator. Below, i am using a cube but that could be your PointObject instead.
Keep in mind that python is slow, using it for point cloud or anything related to particles might be super slow.
from typing import Optional
import c4d
doc: c4d.documents.BaseDocument # The document evaluating this python generator
op: c4d.BaseObject # The python generator
hh: Optional["PyCapsule"] # A HierarchyHelp object, only defined when main is executed
def main() -> c4d.BaseObject:
.
cube = c4d.BaseObject(c4d.Ocube)
parent = c4d.BaseObject(c4d.Onull)
cube.InsertUnder(parent)
matrix = c4d.BaseObject(c4d.Omgmatrix)
matrix.InsertUnder(parent)
matrix[c4d.ID_MG_MOTIONGENERATOR_MODE] = c4d.ID_MG_MOTIONGENERATOR_MODE_OBJECT
matrix[c4d.MG_OBJECT_LINK] = cube
matrix[c4d.MG_POLY_MODE_] = c4d.MG_POLY_MODE_VERTEX
rsTag = matrix.MakeTag(1036222)
rsTag[c4d.REDSHIFT_OBJECT_PARTICLE_MODE] = 2
return parent
Cheers,
Manuel
hi,
for this, you must use the function SetFlags on the Field data and update the data on the object. The flag that must be set is FIELDLIST_FLAGS::CLAMPOUTPUT
Unfortunately, the UI do not update, you must deselect and reselect the object. I did not find anything yet to address this issue.
I used c++ this time to be sure nothing was wrong with python.
BaseObject* op = doc->GetActiveObject();
if (!op)
return maxon::NullptrError(MAXON_SOURCE_LOCATION);
GeData data;
op->GetParameter(FIELDS, data, DESCFLAGS_GET::NONE);
FieldList* fl = static_cast<FieldList*>(data.GetCustomDataType(CUSTOMDATATYPE_FIELDLIST));
fl->SetFlags(FIELDLIST_FLAGS::CLAMPOUTPUT, false);
op->SetParameter(FIELDS, GeData(CUSTOMDATATYPE_FIELDLIST, *fl), DESCFLAGS_SET::NONE);
python code just in case
fl = op[c4d.FIELDS]
toggle = fl.CheckFlag(c4d.FIELDLIST_FLAGS_CLAMPOUTPUT)
fl.SetFlags(c4d.FIELDLIST_FLAGS_CLAMPOUTPUT, not toggle)
op[c4d.FIELDS] = fl
Cheers,
Manuel
hi,
It is not clear what parameter you want to modify. The code you are sharing is inside GetDDescription i suppose.
You need to user SetParameter on your node itself. The tricky part is to build the DescID to target the right parameter.
You need the ID of your fieldList, the ID of the layer on that list and the parameter ID on that layer. All level must be defined as CUSTOMDATATYPE_FIELDLIST.
You must search for the layer inside your field list and call GetUniqueID on it. This will return the correct ID you must use in your DescLevel. Below a script that will flip/flop the parameter "enable/value" for the first layer in the list.
I created a python script for r20 and for more recent version
R20 version
import c4d
def main():
# Called when the plugin is selected by the user. Similar to CommandData.Execute.
effector = op
if effector is None:
raise ValueError("there is no active objet")
fieldList = effector[c4d.FIELDS]
root = fieldList.GetLayersRoot()
clampLayer = root.GetDown()
# Retriving the unique ID allows to construct the DescID to target the right parameter.
clampUniqueID = clampLayer.GetUniqueID()
# Building the DescID
# This ID is composed of three level:
# the first one define the field list parameter,
# the second define the layer, that is why we need its UniqueID,
# the last level define the parameter in this layer.
# Note that all DescLevel are of DataType CUSTOMDATATYPE_FIELDLIST
enableID = c4d.DescID(c4d.DescLevel(c4d.FIELDS, c4d.CUSTOMDATATYPE_FIELDLIST), c4d.DescLevel(clampUniqueID, c4d.CUSTOMDATATYPE_FIELDLIST), c4d.DescLevel(c4d.ID_FIELDLAYER_ENABLE_VALUE, c4d.CUSTOMDATATYPE_FIELDLIST))
# Retreving the value using GetParameter on the effector itself.
value = effector.GetParameter(enableID, c4d.DESCFLAGS_GET_NONE)
# Define the oposite value
effector.SetParameter(enableID, not value, c4d.DESCFLAGS_SET_NONE)
c4d.EventAdd()
if __name__ == '__main__':
main()
Same example but for more recent version of c4d.
from typing import Optional
import c4d
doc: c4d.documents.BaseDocument # The active document
op: Optional[c4d.BaseObject] # The active object, None if unselected
def main() -> None:
# Called when the plugin is selected by the user. Similar to CommandData.Execute.
effector :c4d.BaseObject = op
if effector is None:
raise ValueError("there is no active objet")
fieldList :c4d.FieldList = effector[c4d.FIELDS]
root :c4d.GeListHead = fieldList.GetLayersRoot()
clampLayer :c4d.modules.mograph.FieldLayer = root.GetDown()
# Retriving the unique ID allows to construct the DescID to target the right parameter.
clampUniqueID = clampLayer.GetUniqueID()
# Building the DescID
# This ID is composed of three level:
# the first one define the field list parameter,
# the second define the layer, that is why we need its UniqueID,
# the last level define the parameter in this layer.
# Note that all DescLevel are of DataType CUSTOMDATATYPE_FIELDLIST
enableID :c4d.DescID = c4d.DescID(c4d.DescLevel(c4d.FIELDS, c4d.CUSTOMDATATYPE_FIELDLIST), c4d.DescLevel(clampUniqueID, c4d.CUSTOMDATATYPE_FIELDLIST), c4d.DescLevel(c4d.ID_FIELDLAYER_ENABLE_VALUE, c4d.CUSTOMDATATYPE_FIELDLIST))
# Retreving the value using GetParameter on the effector itself.
value : bool = effector.GetParameter(enableID, c4d.DESCFLAGS_GET_NONE)
# Define the oposite value
effector.SetParameter(enableID, not value, c4d.DESCFLAGS_SET_NONE)
c4d.EventAdd()
if __name__ == '__main__':
main()
Cheers,
Manuel
hi,
if you defined TAG_MULTIPLE when you register your tag, the tag will not be displayed in the attribut manager when you select the object.
Other than that, i did not found anything special to do.
With the latest version (i think it was introduced in r25) you have a parameter on each tag to expose the tag on the object ID_EXPOSETAB
that must be set to true.
Cheers,
Manuel
hi,
In c4d there is no edge, edges are just defined by being connected between two points.
After trying a lot of things i finally found something that looks to work.
It is a combinaison of many ideas. The idea is to count the number of times a point is present on a polygon and the number of times this point is present on a ngon's edge. Make the difference and just keep the point that have a difference of 2 so this point is connected to only two edges.
from typing import Optional
import c4d
doc: c4d.documents.BaseDocument # The active document
op: Optional[c4d.BaseObject] # The active object, None if unselected
# Tries to get the points that are only connected to two edge and part of NGons
def main() -> None:
polys = op.GetAllPolygons()
# GetNgonEdgesCompact will give a list of value for each polygon.
# Those correspond to a binary value where 1 mean the edge is part of an ngon
# and 0 mean no. Be careful that for Triangle edge 2 must not be taken into account.
# edge 3 2 1 0
# - - - - = 0
# - - - x = 1
# - - x - = 2
# - - x x = 3
# - x - - = 4
# - x - x = 5
# - x x - = 6
# - x x x = 7
# x - - - = 8
# x - - x = 9
# x - x - = 10
# x - x x = 11
# x x - - = 12
# x x - x = 13
# x x x - = 14
# x x x x = 15
ngonEC = op.GetNgonEdgesCompact()
pointCount = op.GetPointCount()
# Array allowing to count, for each point, the number of time this point is present in an polygon
# and the number of time this point is present in an ngon edge.
pointNgonDict = {}
pointPolyDict = {}
answer = []
for index in range(0, pointCount):
pointNgonDict[index] = 0
pointPolyDict[index] = 0
# Prepare a neighbor object so we can check if the edge is marked or not avoiding
# to count mutiple time an edge
nb = c4d.utils.Neighbor()
nb.Init(op)
# For each polygon
for polyIndex, cPoly in enumerate(polys):
pli = nb.GetPolyInfo(polyIndex)
# mark those points as part of the polygon
pointPolyDict[cPoly.a] += 1
pointPolyDict[cPoly.b] += 1
pointPolyDict[cPoly.c] += 1
if cPoly.IsTriangle():
# here the edge are really 0, 1, 3 we do not use 2.
for edgeIndex in [0, 1, 3]:
# To avoid counting twice an edge only check those that are marked as false.
if pli["mark"][edgeIndex] == False:
# If the bit of this edge is set to 1, that mean this edge is on a ngon
if ngonEC[polyIndex] & (1 << edgeIndex):
p1, p2 = cPoly.EdgePoints(edgeIndex)
pointNgonDict[p1] += 1
pointNgonDict[p2] += 1
else:
# we include the fourth point.
pointPolyDict[cPoly.d] +=1
# same as the triangle but with all index 0, 1, 2, 3
for edgeIndex in [0, 1, 2, 3]:
if pli["mark"][edgeIndex] == False:
if ngonEC[polyIndex] & (1 << edgeIndex):
p1, p2 = cPoly.EdgePoints(edgeIndex)
pointNgonDict[p1] += 1
pointNgonDict[p2] += 1
# We calculate the difference between those two array and put in the result
# array only points index that are
# in 2 more polygons than the number of edge present on a ngon this point is part of.
for i, (j, k) in enumerate(zip(pointPolyDict.values(), pointNgonDict.values())):
# Check i the point is at least in one ngon with k > 0
if k > 0 andj - k == 2:
answer.append(i)
print (pointNgonDict)
print (pointPolyDict)
print(answer)
if __name__ == '__main__':
main()
Cheers,
Manuel
hi,
unfortunately, there is nothing in the SDK that will help you in this regard. The only solution i see is to check the type of the layer and hardcode if that layer can have a link or not. Could be just an array containing the IDs of layers that do not have this link.
Cheers,
Manuel
Hi,
Instead of reinventing the wheel, as you said, you can use the already existing function. This can be triggered by sending the right message the the SceneHook that is responsable to this mesh check tool. The only thing you need is to build a DescriptionCommand that just need to have its _descId
defined.
The scenehook ID is not available you have to define it. The message ID that correspond to the button clicked can be found in our documentation.
As DescriptionCommand is not available in python, it is not possible to do that using Python. I am afraid there is no solution neither checking yourself as ngon management in python is not possible.
#include "lib_description.h"
#include "dmodeling.h"
#define ID_MESHCHECK 431000027
static maxon::Result<void> PC14455(BaseDocument* doc)
{
iferr_scope;
BaseSceneHook* meshHook = doc->FindSceneHook(ID_MESHCHECK);
if (meshHook)
{
DescriptionCommand dcu;
dcu._descId = MESH_CHECK_EDGEPOINT_SELECT;
meshHook->Message(MSG_DESCRIPTION_COMMAND, &dcu);
}
return maxon::OK;
}
Cheers,
Manuel