Your browser does not seem to support JavaScript. As a result, your viewing experience will be diminished, and you have been placed in read-only mode.
Please download a browser that supports JavaScript, or enable it if it's disabled (i.e. NoScript).
Hello, I made this graphic of default R21 COLOR Constants as a guide for me to find the right colors and wanted to share it with the community: https://imgur.com/a/H11thSt
The spaces where there is no text means that the attribute is the same as c4d.COLOR_BG and is blending in with the background.
c4d.COLOR_BG
Also, while making this, I found that the documentation lists one attribute that throws an error: c4d.COLOR_TIMELINE
c4d.COLOR_TIMELINE
AttributeError: 'module' object has no attribute 'COLOR_TIMELINE'
@m_adam said in Pycharm - Invalid Python SDK:
Hi, @blastframe thanks for contact us. So far we never encounter this issue and we are not able to reproduce it so few ideas to try: Make sure you have permission to write in the Cinema 4D folder and subfolder. Flush the cache, https://stackoverflow.com/a/45099651. Send us your PyCharm log, see https://intellij-support.jetbrains.com/hc/en-us/articles/207241085-Locating-IDE-log-files Cheers, Maxime.
Hi, @blastframe thanks for contact us.
So far we never encounter this issue and we are not able to reproduce it so few ideas to try:
Cheers, Maxime.
Hi @m_adam, Thank you for the reply. I ran PyCharm as an Administrator and flushed the cache. I still received the same error. When I looked at the log, I found a lot of missing file errors, which made me think I had the python.exe (c4dpy.exe) file in the wrong location. When I moved the file to the same folder as the R20 .exe, PyCharm started working. I was confused about the file location because, while in the Install instructions from the C4D Python SDK Documentation has the c4dpy in the C4D folder, this image made it seem like it could exist in a separate folder:
I would have expected a file location similar to the default Windows location: C:\Program Files\MAXON\Cinema 4D R20\python.exe
Thank you again. I have one follow-up question that I'll ask as a new topic.
Hi @jhpark! I don't work for the SDK team, but I believe the script below will do what you want.
Some quick notes about posting:
When entering your code into a post on this forum, make sure you hit this button first It creates code tags in your post. Put your code in between those and then it will format your code automatically.
Also, after submitting, hit the button Topic Tools at the bottom right of your post to Ask as Question.
When someone has answered your question correctly, click this button at the bottom of their post. This makes it clear to the moderators when the question has been correctly answered.
Here's the code. Because you were using ID_BASEOBJECT_COLOR, I was unsure if you wanted the object's display color or the material color (they are two different things), but I wrote this for the sphere's texture tags' material's color. Also, the code is for the spheres' relative position. More work would need to be done to get the animating position track values into global space.
ID_BASEOBJECT_COLOR
import c4d from c4d import gui def GetNextObject(op): #function for navigating the hierarchy if op==None: return None if op.GetDown(): return op.GetDown() while not op.GetNext() and op.GetUp(): op = op.GetUp() return op.GetNext() c4d.EventAdd() def getPreviewRange(doc,fps): #returns the active preview range fps = doc.GetFps() fromTime = doc.GetLoopMinTime().GetFrame(fps) toTime = doc.GetLoopMaxTime().GetFrame(fps)+1 return [fromTime,toTime] def convertVecToRgb(vector): #converts vector to rgb list return [vector[0]*255,vector[1]*255,vector[2]*255] def main(doc): fps = doc.GetFps() previewRange = getPreviewRange(doc,fps) #rather than needing to set frames manually, you can simply resize your preview range. frame_count = previewRange[1]-previewRange[0] # this section navigates the hierarchy and saves all of the spheres to a list called 'output' # it's better to do this than to use doc.SearchObject in the case you have multiple spheres with the same name obj = doc.GetFirstObject() if obj==None: gui.MessageDialog('There are no objects in the scene.') return output = [] while obj and obj!=None: if obj.GetType() == c4d.Osphere: output.append(obj) obj = GetNextObject(obj) if len(output) == 0: gui.MessageDialog('There are no spheres in the scene.') # loops through spheres in the scene for sphere in output: #prints a separating line to the console print '#' * 80 for f in range(previewRange[0], previewRange[1]): doc.SetTime(c4d.BaseTime(0, doc[c4d.DOCUMENT_FPS])) keyTime = c4d.BaseTime(f,fps) #get the current frame # POSITION pTracks = sphere.GetCTracks() #get the sphere's animating tracks pos = [sphere.GetMl().off.x,sphere.GetMl().off.y,sphere.GetMl().off.z] #get the sphere's default relative position #replace those values with the animating ones. for t in pTracks: descid = t.GetDescriptionID() #get the track's id if descid[0].id == c4d.ID_BASEOBJECT_REL_POSITION: #see if it matches the object's position track curve = t.GetCurve() #get the track's animation curve keyvalue = curve.GetValue(keyTime, fps) #get the animation curve's value at the current frame if descid[1].id == c4d.VECTOR_X: pos[0] = keyvalue #add to x elif descid[1].id == c4d.VECTOR_Y: pos[1] = keyvalue #add to x elif descid[1].id == c4d.VECTOR_Z: pos[2] = keyvalue #add to z # MATERIAL COLOR tags = sphere.GetTags() #get sphere's tags matColor = [] #create material color list for tag in tags: #loop through sphere's tags if tag.GetType() == c4d.Ttexture: #check if tag is a texture tag mat = tag.GetMaterial() #if yes, get the tag's material tracks = mat.GetCTracks() #get the material's animating tracks for t in tracks: descid = t.GetDescriptionID() #get the track's id if descid[0].id == c4d.MATERIAL_COLOR_COLOR: #see if it matches the material color track curve = t.GetCurve() #get the track's animation curve keyvalue = curve.GetValue(keyTime, fps) #get the animation curve's value at the current frame matColor.append(keyvalue*255) #add r,g,b to matColor if len(tracks) == 0: #in case it's not animating, use general Color matColor = convertVecToRgb(mat[c4d.MATERIAL_COLOR_COLOR]) # I prefer using string formatting with the placeholder %s for strings, %d for numbers, # and the % as the replacement operator print("Name: %s, Frame: %d, Position (x,y,z): %d,%d,%d, Material Color (r,g,b): %d,%d,%d"%( sphere.GetName(),f,pos[0],pos[1],pos[2], matColor[0],matColor[1],matColor[2])) if __name__=='__main__': # rather than using documents.GetActiveDocument, I found that you can pass a reference to the document using this method main(doc)
Here's a scene file where the object's display colors and material colors are different. The display colors are visible in the viewport, but you will see the material color if you render. Spheres.c4d
Hi @woodstar There's definitely a way! What did you want to connect in Xpresso with Python? I wrote a script that does some of what you described: it prompts the user for a camera name, then creates an Xpresso rig based on that. The rig connects a user data checkbox 'Follow Target' to a couple of properties in the rig (Target tag's 'Enable' and the camera's 'Use Target Object''s checkbox for setting the Focus Distance). That would allow you to follow an object for some of the camera move and then keyframe it off.
""" Name-US:Xpresso Camera Rig Description-US:Creates an Xpresso Camera Rig author:blastframe credits: Creating User Data - https://www.cineversity.com/wiki/Python%3A_User_Data/ Python Xpresso - https://www.cineversity.com/vidplaytut/xpresso_maker_overview """ import c4d from c4d import gui def create_user_data_group(obj, name, parentGroup=None, columns=None, shortname=None): # see Creating User Data url above if obj is None: return False if shortname is None: shortname = name bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_GROUP) bc[c4d.DESC_NAME] = name bc[c4d.DESC_SHORT_NAME] = shortname bc[c4d.DESC_TITLEBAR] = 1 if parentGroup is not None: bc[c4d.DESC_PARENTGROUP] = parentGroup if columns is not None: bc[22] = columns return obj.AddUserData(bc) def create_user_data_bool(obj, name, val=True, parentGroup=None): # see Creating User Data url above if obj is None: return False bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_BOOL) bc[c4d.DESC_NAME] = name bc[c4d.DESC_SHORT_NAME] = name bc[c4d.DESC_DEFAULT] = val bc[c4d.DESC_ANIMATE] = c4d.DESC_ANIMATE_ON if parentGroup is not None: bc[c4d.DESC_PARENTGROUP] = parentGroup element = obj.AddUserData(bc) obj[element] = val return element def connect_xpresso(camera,camera_truck_ctrl,targetTag): # Xpresso documentation: # https://developers.maxon.net/docs/Cinema4DPythonSDK/html/modules/c4d.modules/graphview/index.html xtag = camera_truck_ctrl.MakeTag(c4d.Texpresso) # create Xpresso tag doc.AddUndo(c4d.UNDOTYPE_NEW, xtag) # A Graph View Node Master stores a collection of Graph View Nodes. gv = xtag.GetNodeMaster() # create node for the camera truck control camera_truck_ctrlNode = gv.CreateNode(parent=gv.GetRoot(), id=c4d.ID_OPERATOR_OBJECT, insert=None, x=100, y=0) doc.AddUndo(c4d.UNDOTYPE_NEW, camera_truck_ctrlNode) doc.AddUndo(c4d.UNDOTYPE_CHANGE, camera_truck_ctrlNode) udPort = None # create output port for the user data boolean (from Rick Barrett script) for id, bc in camera_truck_ctrl.GetUserDataContainer(): if bc[c4d.DESC_CUSTOMGUI] is not None and \ bc[c4d.DESC_CUSTOMGUI] is not c4d.CUSTOMGUI_SEPARATOR: doc.AddUndo(c4d.UNDOTYPE_CHANGE,camera_truck_ctrlNode) udPort = camera_truck_ctrlNode.AddPort(c4d.GV_PORT_OUTPUT, id) # create node for the camera Target tag targetNode = gv.CreateNode(gv.GetRoot(), c4d.ID_OPERATOR_OBJECT, insert=None, x=300, y=0) # get DescID for Target tag's Enable property targetEnabled = c4d.DescID(c4d.DescLevel(c4d.EXPRESSION_ENABLE)); doc.AddUndo(c4d.UNDOTYPE_NEW, targetNode) # after creating node we need to give it an object targetNode[c4d.GV_OBJECT_OBJECT_ID] = targetTag # add Target node Enabled input port enablePort = targetNode.AddPort(c4d.GV_PORT_INPUT, targetEnabled) # create node for the camera cameraNode = gv.CreateNode(parent=gv.GetRoot(), id=c4d.ID_OPERATOR_OBJECT, insert=None, x=300, y=100) # after creating node we need to give it an object cameraNode[c4d.GV_OBJECT_OBJECT_ID] = camera # add camera input port for using the Target tag's object as the Focus Object useTargetObjectPort = cameraNode.AddPort(c4d.GV_PORT_INPUT, c4d.CAMERAOBJECT_USETARGETOBJECT) # connect the ports udPort.Connect(enablePort) udPort.Connect(useTargetObjectPort) # refresh the Graph View c4d.modules.graphview.RedrawMaster(gv) def main(doc): doc.StartUndo() camera_truck_ctrl = c4d.BaseObject(c4d.Osplinenside) # Create camera_truck_ctrl control camera_truck_ctrl[c4d.ID_BASEOBJECT_USECOLOR] = 2 # turn on display color camera_truck_ctrl[c4d.ID_BASEOBJECT_COLOR] = c4d.Vector(0,1,1) # set display color doc.InsertObject(camera_truck_ctrl) doc.AddUndo(c4d.UNDOTYPE_NEW, camera_truck_ctrl) cameraControls = create_user_data_group(camera_truck_ctrl,"Camera Controls",c4d.DescID(0)) # create user data group followTargetBool = create_user_data_bool(camera_truck_ctrl,"Follow Target",True,cameraControls) # Follow Target boolean checkbox camera = c4d.BaseObject(c4d.Ocamera) # Create camera name = gui.InputDialog("What would you like to name your camera?", "Camera") # prompt user for name camera.SetName(name) camera_truck_ctrl.SetName("%s_con+"%name) # use camera name to name control targetTag = camera.MakeTag(c4d.Ttargetexpression) # create Target tag doc.AddUndo(c4d.UNDOTYPE_NEW, targetTag) focusObject = c4d.BaseObject(c4d.Onull) # Create focus object focusObject.SetName("%s Target"%name) focusObject[c4d.ID_BASEOBJECT_USECOLOR] = 2 # turn on display color focusObject[c4d.ID_BASEOBJECT_COLOR] = c4d.Vector(1,0,0) # set display color focusObject[c4d.ID_BASEOBJECT_REL_POSITION,c4d.VECTOR_Z] = 500 # set focus object's position Z to 500 focusObject[c4d.NULLOBJECT_DISPLAY] = 13 # set null target to display as sphere focusObject[c4d.NULLOBJECT_RADIUS] = 30 # set sphere radius to 30 focusObject[c4d.NULLOBJECT_ORIENTATION] = 1 # set sphere plane to XY targetTag[c4d.TARGETEXPRESSIONTAG_LINK] = focusObject # assign focus object to Target tag object link # insert objects to document doc.InsertObject(focusObject) doc.AddUndo(c4d.UNDOTYPE_NEW, focusObject) doc.InsertObject(camera_truck_ctrl) doc.AddUndo(c4d.UNDOTYPE_NEW, camera_truck_ctrl) camera.InsertUnder(camera_truck_ctrl) # parent to camera_truck_ctrl doc.AddUndo(c4d.UNDOTYPE_NEW, camera) connect_xpresso(camera,camera_truck_ctrl,targetTag) # create Xpresso connections doc.SetActiveObject(camera_truck_ctrl,c4d.SELECTION_NEW) # select camera truck control c4d.EventAdd() doc.EndUndo() if __name__=='__main__': main(doc)
Hello @esan ! I'm not entirely sure what the purpose of your script is, so I made changes to what you provided. You're probably already aware, but this script will only work with children of your selected object, not grandchildren.
The biggest changes were:
import c4d from c4d import gui def createInstance(obj): inst = c4d.BaseObject(c4d.Oinstance) # created Instance with Base Object inst[c4d.INSTANCEOBJECT_LINK] = obj # set Instance's reference link # set the name using the object passed to the function inst[c4d.ID_BASELIST_NAME] = "%s Instance"%obj[c4d.ID_BASELIST_NAME] return inst # return the Instance instance def AddLongDataType(obj): # create User Data Container named Picker if obj is None: return bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_LONG) bc[c4d.DESC_NAME] = "Picker" doc.AddUndo(c4d.UNDO_CHANGE, obj) obj.AddUserData(bc) c4d.EventAdd() def setQuickTab(obj, data): # change User Date container to type Cycle, populate with Children Names descid = data[0][0] bc = data[0][1] children = obj.GetChildren() # Build new cycle options container dynamically cycle = c4d.BaseContainer() for i,child in enumerate(children): cycle.SetData(i, child.GetName()) bc[c4d.DESC_CYCLE] = cycle doc.AddUndo(c4d.UNDO_CHANGE, obj) # Set modified description data container obj.SetUserDataContainer(descid, bc) def main(doc): obj = doc.GetActiveObject() if (obj == None): gui.MessageDialog("Please select an object.") return doc.StartUndo() AddLongDataType(obj) data = obj.GetUserDataContainer() setQuickTab(obj, data) inst = createInstance(obj) doc.InsertObject(inst) doc.AddUndo(c4d.UNDO_NEW, inst) doc.EndUndo() c4d.EventAdd() if __name__=='__main__': main(doc)
Hi @bentraje! I found this documentation in the C++ manual that says:
"To start and end an undo based on some interaction in a GeDialog the messages BFM_INTERACTSTART and BFM_INTERACTEND can be used."
I tried this in the GeDialog and got a TypeError:
def Message(self, msg, result): if msg.GetId()==c4d.BFM_INTERACTSTART: print("Interact Start") doc.AddUndo(c4d.UNDOTYPE_CHANGE,self) elif msg.GetId()==c4d.BFM_INTERACTEND: print("Interact End") return gui.GeDialog.Message(self, msg, result)
TypeError: argument 2 must be c4d.GeListNode
I don't think I'm setting up the Undo correctly because what can be passed to doc.AddUndo from the GeDialog that is GeListNode? Without the doc.AddUndo line, those message conditionals do work.
doc.AddUndo
Hello, I think I've found a small error in the documentation:
In BaseBitmap.ScaleIt it lists the final argument as inprop. While using this:
inprop
orig.ScaleIt(newBitmap, intens=256, sample=True, inprop=False)
I got the following error: TypeError: Required argument 'nprop' (pos 4) not found
TypeError: Required argument 'nprop' (pos 4) not found
When I changed the argument to nprop, it worked.
nprop
Thanks.
@m_adam This is nice work, Maxime, thank you! I have to check out weakref
I'm getting a couple of errors when clicking in a part of the GeUserArea without a square or hitting a key on the keyboard. How can I handle these?
No square:
line 331, in InputEvent currentIndex = self.draggedObj.GetParentedIndex() AttributeError: 'NoneType' object has no attribute 'GetParentedIndex'`
Keyboard input:
line 278, in InputEvent self.MouseDragStart(c4d.KEY_MLEFT, mouseX, mouseY, c4d.MOUSEDRAGFLAGS_DONTHIDEMOUSE | c4d.MOUSEDRAGFLAGS_NOMOVE) TypeError: a float is required
@m_adam & @PluginStudent Thank you both! This was exactly what I needed. This forum is so so helpful.
@m_adam Hi Maxime, I did try your example and I saw it working as a gadget but, you're right, I want to communicate between two GeDialogs. I followed your advice on using CoreMessage and found success with SpecialEventAdd. Thank you!
Hello, I noticed that there are no Sketch and Toon shader symbols in the Python API. Why is that?
Here are the IDs for anyone needing them:
Art = 1012161 Cel = 1012158 Hatch = 1012166 Spots = 1012160
They are in the C++ shader symbols as xartshader, xcelshader, xhatchingshader, and xspotshader.
xartshader
xcelshader
xhatchingshader
xspotshader
Here are the Python Shader Symbols:
c4d.Xambientocclusion = 1001191 c4d.Xbase = 5707 c4d.Xbitmap = 5833 c4d.Xbrick = 5804 c4d.Xchanlum = 1007539 c4d.Xcheckerboard = 5800 c4d.Xcloud = 5802 c4d.Xcolor = 5832 c4d.Xcolorizer = 1011112 c4d.Xcolorstripes = 5822 c4d.Xcyclone = 5821 c4d.Xdistorter = 1011114 c4d.Xearth = 5825 c4d.Xfalloff = 1011101 c4d.Xfilter = 1011128 c4d.Xfire = 5803 c4d.Xflame = 5817 c4d.Xformula = 1031433 c4d.Xfresnel = 1011103 c4d.Xfusion = 1011109 c4d.Xgalaxy = 5813 c4d.Xgradient = 1011100 c4d.Xlayer = 1011123 c4d.Xlumas = 1011105 c4d.Xmarble = 5830 c4d.Xmetal = 5827 c4d.Xmosaic = 1022119 c4d.Xnoise = 1011116 c4d.Xnormaldirection = 1011107 c4d.Xnormalizer = 1026588 c4d.Xobjectcolor = 1033961 c4d.Xplanet = 5829 c4d.Xposterizer = 1011111 c4d.Xprojector = 1011115 c4d.Xproximal = 1011106 c4d.Xrainsampler = 1026576 c4d.Xripple = 1011199 c4d.Xrust = 5828 c4d.Xsimplenoise = 5807 c4d.Xsimpleturbulence = 5806 c4d.Xskyshader = 1000 c4d.Xspectral = 5831 c4d.Xspline = 1011124 c4d.Xsss = 1001197 c4d.Xstar = 5816 c4d.Xstarfield = 5808 c4d.Xsubstance = 1032171 c4d.Xsunburst = 5820 c4d.Xterrainmask = 1026277 c4d.Xthinfilm = 1035731 c4d.Xtiles = 1011102 c4d.Xtranslucency = 1011108 c4d.Xvariation = 1033825 c4d.Xvenus = 5826 c4d.Xvertexmap = 1011137 c4d.Xwater = 5818 c4d.Xwood = 5823 c4d.Xxmbsubsurface = 1025614
Thank you.
@m_magalhaes Yes, I think the error was because Octane was the result of rdata.GetFirstVideoPost() and those symbols don't exist in it. If I check for the Viewport Renderer, it seems to work fine. Thanks for the reply and symbol tip!
rdata.GetFirstVideoPost()
I think I have discovered the issue: I believe rdata.GetFirstVideoPost() was getting a renderer other than the Viewport Renderer, so those symbols weren't available. I am now using the following (I found it here) to check that the video post is the Viewport Renderer.
post = rdata.GetFirstVideoPost() while post is not None: if post.CheckType(300001061): break post = post.GetNext() if post is None: rd.InsertVideoPost(c4d.BaseList2D(300001061))
Unless the API devs have any corrections, I'll mark this as solved.
Hello, I am trying to change the Viewport Renderer parameters. I was able to use the method illustrated in the code sample below, but something changed (I believe in the API) and this now leads to TypeError: __setitem__ expected str, not bool. If I'm not to assign these values as a boolean, can someone please advise on how these are to be set? Thank you.
TypeError: __setitem__ expected str, not bool
import c4d from c4d import documents def main(): doc = documents.GetActiveDocument() rdata = doc.GetActiveRenderData() rd = rdata.GetData() post = rdata.GetFirstVideoPost() post[c4d.VP_PREVIEWHARDWARE_DISPLAYFILTER_NGONLINES] = False post[c4d.VP_PREVIEWHARDWARE_DISPLAYFILTER_OTHER] = False post[c4d.VP_PREVIEWHARDWARE_DISPLAYFILTER_SDSCAGE] = False post[c4d.VP_PREVIEWHARDWARE_DISPLAYFILTER_ONION] = False if __name__=='__main__': main()
Hello, When I create a GeDialog with buttons, the first button is highlighted by default for some reason:
import c4d from c4d import gui MY_PLUGIN_ID = 1234567 GROUP1_ID = 1000 GROUP2_ID = 1001 BUTTON1_ID = 1002 BUTTON2_ID = 1003 BUTTON3_ID = 1004 class Example_Dialog(gui.GeDialog): def CreateLayout(self): self.SetTitle("BUTTON_HIGHLIGHTED") self.GroupBorderSpace(10,10,10,10) if self.GroupBegin(GROUP1_ID, c4d.BFH_SCALEFIT | c4d.BFV_SCALE | c4d.BFV_CENTER): if self.GroupBegin(GROUP2_ID, c4d.BFH_SCALEFIT, 1): self.GroupBorderSpace(5,5,5,5) self.AddButton(BUTTON1_ID, c4d.BFH_SCALEFIT | c4d.BFV_SCALE | c4d.BFV_CENTER, name='Button1') self.AddButton(BUTTON2_ID, c4d.BFH_SCALEFIT | c4d.BFV_SCALE | c4d.BFV_CENTER, name='Button2') self.AddButton(BUTTON3_ID, c4d.BFH_SCALEFIT | c4d.BFV_SCALE | c4d.BFV_CENTER, name='Button3') self.GroupEnd() self.GroupEnd() return True # Main function def main(): global example_dialog example_dialog = Example_Dialog() example_dialog.Open(c4d.DLG_TYPE_ASYNC, pluginid=MY_PLUGIN_ID, defaultw=300, defaulth=150) # Execute main() if __name__=='__main__': main()
@ferdinand Thank you. I'll try to cache myself.
@ferdinand Hi, thank you for the reply. I won't need particles. This is a polygon object with a deformer whose point positions I need to get over the Preview Range.
Isn't the crawl you're describing the same as the range loop in the basedocument_read_animated_mesh.py? My question is: is there a more performant way if this needs to be calculated on each frame?
If this is the only way, since the animation keyframes would be on another object (the deformer, joints, or a controller) and not a point level animation, I believe the position will depend upon all of the ExecutePasses (animation, expressions, and caches) as in the example. Unless the deformed cache stores these points between playback?
ExecutePasses
@ferdinand Thank you for the prompt reply.
In my project I need to get the entire timeline and be able to scrub it. Can you please explain the "crawl" method more?
I was able to remove the EventAdd from my expression tag. I tried removing ExecutePasses() and it the deformation animation froze.
ExecutePasses()
@ferdinand Thank you for the reply. I mentioned in the title, but (my apologies), I didn't specify in the body of my topic or code example: Is it possible to detect when there a new keyframe has been added for transformation from a tag plugin?
I found this post on CGSociety where @m_adam shares a link to this file: basedocument_read_animated_mesh.py. Awesome stuff!
I've got it working, but I'm curious: do I need to use doc.SetTime() to get all the values over time at once?
This seems like a resource-expensive process for my project because I'm using it in a tag.