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).
Hi @jenandesign, first of all, welcome in the plugincafe community.
No worry at all, since it's your first post, but I would like to point you about few rules in order to properly ask questions in this forum for future ones (I've set up this one).
With that's said. Regarding your question. I get it to work (a bit hacky) but it's working. When C4D render a scene, actually the whole scene is cloned in memory and then it renders this one. (It allow a user to still work on C4D while the other scene is rendering). So this render scene is read-only, you shouldn't modify at anytime anything in this scene, or you can screw up the render. Moreover in a shaderData, in order to optimize memory, only relevant information (aka polygon, UV) are copied. So you are losing your UserData. This means you have to find another way to find your effector. So the way I find to achieve what you want is to get the document from the object passed to the shader(the rendered document). Then in this document, I search for an effector called "Plain" (Not the most robust way but it's working).
Then here the final code I put on the python color channel of a sketch and toon
import c4d def main(): doc = op.GetDocument() effector = doc.SearchObject("Plain") if not effector: return c4d.Vector() eff = c4d.modules.mograph.C4D_Falloff() if not eff: return c4d.Vector() eff.InitFalloff(bc=effector.GetData()) eff.SetMg(effector.GetMg()) eff.SetMode(effector[c4d.FALLOFF_MODE]) # World position currently sampled value = eff.Sample(wPnt, False) return c4d.Vector(value)
And the attached scene where a plain effector drives a mograph effector and the sketch and toon shader. 0_1542626486830_SketchAndToon.c4d
If you have any questions, please let me know! Cheers, Maxime!
Hi @merkvilson, I indeed overlooked the issue, since in all topic it's only Python 3.6, I didn't tried.
But in MacOs, certifi is not installed (which cause the issue), but you can directly get the certificate file to make it works as expected. Here the code which works on mac and windows.
import c4d import os import ssl import urllib f = os.path.join(os.path.dirname(c4d.storage.GeGetStartupApplication()), "resource", "ssl", "cacert.pem") context = ssl.create_default_context(cafile=f) urllib.urlopen("https://google.com",context=context)
Or with urllib2
import c4d import urllib2 import os f = os.path.join(os.path.dirname(c4d.storage.GeGetStartupApplication()), "resource", "ssl", "cacert.pem") urllib2.urlopen("https://google.com", cafile=f)
Hi @merkvilson first of all pip and Niklas localimport module is completely two different things.
Now how to install pip?
cmd
c4dpy get-pip.py
c4dpy -m pip install numpy
With that's said we do not provide support for 3rd party modules, so if it does not work with Cinema 4D Python environment, we can't help you.
Cheers, Maxime.
Hi @kisaf,
MCOMMAND_MIRROR is indeed a bit special because when you click on the Apply button from the GUI, it does send a message to some c4d elements, then these elements read MDATA_MIRROR_VALUE, MDATA_MIRROR_SYSTEM and MDATA_MIRROR_PLANE calculates values for MDATA_MIRROR_POINT and MDATA_MIRROR_VECTOR and then call the SendModelingCommand. Unfortunately, there is no way for you to send this message, and the best way to do it is to recalculate the stuff as bellow.
import c4d def main(): # Checks if selected object is valid if op is None: raise ValueError("op is none, please select one object.") # Defines some general settings, take care MDATA_MIRROR_VALUE, MDATA_MIRROR_SYSTEM and MDATA_MIRROR_PLANE settings = c4d.BaseContainer() settings[c4d.MDATA_MIRROR_DUPLICATE] = True settings[c4d.MDATA_MIRROR_SELECTIONS] = True settings[c4d.MDATA_MIRROR_ONPLANE] = True settings[c4d.MDATA_MIRROR_WELD] = True settings[c4d.MDATA_MIRROR_TOLERANCE] = 0.01 # Corresponds to MDATA_MIRROR_VALUE, MDATA_MIRROR_SYSTEM and MDATA_MIRROR_PLANE value = 1000.0 system = 0 # 0 = Object, 1 = World plane = 1 # 0 = XY, 1 = ZY, 2 = XZ if not 0 <= system < 2: raise ValueError("System can only be 0 or 1") # Buffer position vector pos = c4d.Vector() # Calculates Local (Object) coordinates if system == 0: globalMatrix = op.GetMg() if plane == 0: pos = globalMatrix.v3 elif plane == 1: pos = globalMatrix.v1 elif plane == 2: pos = globalMatrix.v2 settings[c4d.MDATA_MIRROR_POINT] = globalMatrix.off + pos * value settings[c4d.MDATA_MIRROR_VECTOR] = pos # Calculates World coordinates elif system == 1: if plane == 0: pos = c4d.Vector(0.0, 0.0, 1.0) elif plane == 1: pos = c4d.Vector(1.0, 0.0, 0.0) elif plane == 2: pos = c4d.Vector(0.0, 1.0, 0.0) settings[c4d.MDATA_MIRROR_POINT] = pos * value settings[c4d.MDATA_MIRROR_VECTOR] = pos # Send the Modeling Operation res = c4d.utils.SendModelingCommand(c4d.MCOMMAND_MIRROR, [op], c4d.MODELINGCOMMANDMODE_ALL, settings, doc) c4d.EventAdd() if __name__ == "__main__": main()
Hope it solves your issue, if you have any question, please let me know. Cheers, Maxime.
So regarding your script why it crashes, it's simply because you make an infinite loop with this code since you add each time something more to iterate.
for idx in newPos: newPos.append(GlobalToLocal(oldObject, idx))
Just to make it cleaner I've called objA and objB. Then the needed steps are:
So as you may be already aware regarding our Matrix Fundamentals where is how to achieve both operations.
Finally, before providing with the solution I would like to remind you to please always execute your code in the main function. The main reason is that if you transform your script as a button in the Dialog, every code that is in the global scope of your script is executed for each redraw of the Cinema 4D GUI (which is pretty intensive!). Moreover please always check for objects, if they exist or not try to adopt a defensive programming style in order to avoid any issues.
import c4d def main(): # Declares object variables ObjA = doc.SearchObject("A") ObjB = doc.SearchObject("B") # Checks if objects are found if ObjA is None or ObjB is None: raise ValueError("Can't found a and b object") # Checks if they are polygon object if not isinstance(ObjA, c4d.PolygonObject) or not isinstance(ObjB, c4d.PolygonObject): raise TypeError("Objects are not PolygonObject") # Checks Point count is the same for both obj allPtsObjA = ObjA.GetAllPoints() allPtsObjB = ObjB.GetAllPoints() if len(allPtsObjA) != len(allPtsObjB): raise ValueError("Object does not get the same pount count") # Retrieves all points of B in World Position, by multipling each local position of ObjB.GetAllPoints() by ObjB.GetMg() allPtsObjBWorld = [pt * ObjB.GetMg() for pt in allPtsObjB] # Retrieves All points of B from World to Local Position of ObjA, by multipling each world position of ObjB by the inverse objA.GetMg() allPtsObjANewLocal = [pt * ~ObjA.GetMg() for pt in allPtsObjBWorld] # Sets New points position ObjA.SetAllPoints(allPtsObjANewLocal) ObjA.Message(c4d.MSG_UPDATE) # Updates Cinema 4D c4d.EventAdd() # Execute main() if __name__=='__main__': main()
If you have any questions, please let me know. Cheers, Maxime.
Hi @mikeudin, due to how our Classic Python API is built deepcopy does not work since our python objects only store pointer to our internal C++ object and not directly to other Python objects, so deepcopy fail in this regards since, the deepcopy operation, free and try to recreate these data, which is not possible since they are not PyObject but real C++ object but copy.copy does works, but since it's a shallow copy operation it only works for one level (e.g. a dict of dict of DescID will fail).
For more information about the difference please read Shallow vs Deep Copying of Python Objects.
However here a couple methods to copy a dict.
import copy dict1 = {'1':dId_one,'2':dId_two} dict2 = copy.copy(dict1)
dict1 = {'1':dId_one,'2':dId_two} dict2 = dict1.copy()
dict1 = {'1':dId_one,'2':dId_two} dict2 = dict(dict1)
Hi @mike, first of all I would like to remind you to read and use the Q&A functionnality.
Regarding your question, here is an example of a basic GeDialog, which make use to SetFloat to define a range.
import c4d class MonDlg( c4d.gui.GeDialog): idSlider = 1000 idButton = 1001 # Create the Layout def CreateLayout(self): self.AddEditSlider(self.idSlider, c4d.BFH_SCALE|c4d.BFV_SCALE, initw=100, inith=20) self.AddButton(self.idButton, c4d.BFH_SCALE|c4d.BFV_SCALE, initw=100, inith=20,name = 'Get Value') return True # Called after CreateLayout def InitValues(self): self.SetFloat(self.idSlider, 0.25, min=0.0, max=1.0, step=0.01, min2=0.0, max2=0.0) return True # Called for each interaction from a widget def Command(self, id, msg): if id == self.idButton: print self.GetFloat(self.idSlider) return True def main(): dlg = MonDlg() dlg.Open(c4d.DLG_TYPE_MODAL) if __name__=='__main__': main()
If you have any questions please let me know. Cheers, Maxime!
Hi @chuanzhen actually the problem interested me and I took some personal time on my weekend to do some tests.
I did 2 test scenes, which was a cube with 163970 points driven by 3 joints With your original source code 0.53 fps optimize python: 1.09 fps (x2.05) C++: 19.36 fps (x36.52)
And the other one was the same cube but driven by 11 joints. With your original source code 0.37 fps optimize python: 0.64 fps (x1.73) C++: 12.08 (x32.65)
So here is the optimized python code. As you can see I try to avoid as much as possible doing operation in the nested loop.
class newskin(plugins.ObjectData): lasttagdirty = None #for weight change update jointdir = None #for joint change update cachedir = None #for other bindmesh check def CheckDirty(self, op, doc): #check first run if op[c4d.NEWSKINTAG] is None: op.SetDirty(c4d.DIRTYFLAGS_DATA) return #check weight change if op[c4d.NEWSKINTAG].GetDirty(c4d.DIRTYFLAGS_DATA) != self.lasttagdirty: op.SetDirty(c4d.DIRTYFLAGS_DATA) return #check matrix change checktime = op[c4d.NEWSKINTAG].GetJointCount() cdirty = 0 for i in xrange(checktime): cdirty += op[c4d.NEWSKINTAG].GetJoint(i).GetDirty(c4d.DIRTYFLAGS_MATRIX) if cdirty != self.jointdir or self.cachedir != op[c4d.NEWSKINCHECK].GetDirty(c4d.DIRTYFLAGS_CACHE): op.SetDirty(c4d.DIRTYFLAGS_DATA) return return def ModifyObject(self, mod, doc, op, op_mg, mod_mg, lod, flags, thread): if mod[c4d.NEWSKINTAG] is None or mod[c4d.NEWSKINCHECK] is None: return True tag = mod[c4d.NEWSKINTAG] self.lasttagdirty = tag.GetDirty(c4d.DIRTYFLAGS_DATA) #get current weight tag dirty self.cachedir = mod[c4d.NEWSKINCHECK].GetDirty(c4d.DIRTYFLAGS_CACHE) #get bindmesh check plist = [pos * op_mg for pos in op.GetAllPoints()] pcount = op.GetPointCount() jcount = tag.GetJointCount() self.jointdir = 0 for m in xrange(jcount): joint = tag.GetJoint(m) self.jointdir += joint.GetDirty(c4d.DIRTYFLAGS_MATRIX) #all joint current matrix dirtycount temp = c4d.Vector() for n in xrange(pcount): #n:point index temp %= temp for m in xrange(jcount): joint = tag.GetJoint(m) weight = tag.GetWeight(m, n) if not weight : continue cjmg = joint.GetMg() jdict = tag.GetJointRestState(m) jmg = jdict["m_bMg"] jmi = jdict["m_bMi"] temp += weight * cjmg * jmi * plist[n] #defrmer global pos plist[n] = temp plist = [~op_mg * pos for pos in plist] op.SetAllPoints(plist) op.Message(c4d.MSG_UPDATE) return True
And here the C++ version (compatible R20 only). It uses paralellFor, not sure it's really worth didn't do proper profiling about it but I wanted to try them. Moreover please do not consider my C++ code has fully optimized since you could probably improve it depending on the situation.
#include "c4d_symbols.h" #include "main.h" #include "c4d_objectdata.h" #include "lib_ca.h" // Local resources #include "oskinmodifier.h" #include "maxon/parallelfor.h" #include "maxon/basearray.h" /**A unique plugin ID. You must obtain this from http://www.plugincafe.com. Use this ID to create new instances of this object.*/ static const Int32 ID_OBJECTDATA_SKINMODIFIER = 1000002; class SkinModifier : public ObjectData { INSTANCEOF(SkinModifier, ObjectData) public: static NodeData* Alloc() { return NewObj(SkinModifier) iferr_ignore("SkinModifier plugin not instanced"); } virtual void CheckDirty(BaseObject *op, BaseDocument *doc); virtual Bool ModifyObject(BaseObject* mod, BaseDocument* doc, BaseObject* op, const Matrix& op_mg, const Matrix& mod_mg, Float lod, Int32 flags, BaseThread* thread); private: BaseList2D* GetBaseLink(BaseObject* op, DescID id, Int excepted); Int lasttagdirty; /// get current weight tag dirty Int jointdir; Int cachedir; }; void SkinModifier::CheckDirty(BaseObject *op, BaseDocument *doc) { BaseList2D* t = GetBaseLink(op, DescID(NEWSKINTAG), Tweights); BaseList2D* l = GetBaseLink(op, DescID(NEWSKINCHECK), Opolygon); if (!t || !l) return; PointObject* linkOp = static_cast<PointObject*>(l); CAWeightTag* tag = static_cast<CAWeightTag*>(t); // check first run if (!tag) { op->SetDirty(DIRTYFLAGS::DATA); return; } // check weight change if (tag->GetDirty(DIRTYFLAGS::DATA) != lasttagdirty) { op->SetDirty(DIRTYFLAGS::DATA); return; } // check matrix change Int cdirty = 0; for (Int i = 0; i < tag->GetJointCount(); i++) { cdirty += tag->GetJoint(i, tag->GetDocument())->GetDirty(DIRTYFLAGS::MATRIX); if (cdirty != jointdir || cachedir != linkOp->GetDirty(DIRTYFLAGS::CACHE)) { op->SetDirty(DIRTYFLAGS::DATA); return; } } return; } BaseList2D* SkinModifier::GetBaseLink(BaseObject* op, DescID id, Int excepted) { GeData data; if (!op->GetParameter(id, data, DESCFLAGS_GET::NONE)) return nullptr; return data.GetLink(op->GetDocument(), excepted); } Bool SkinModifier::ModifyObject(BaseObject* mod, BaseDocument* doc, BaseObject* op, const Matrix& op_mg, const Matrix& mod_mg, Float lod, Int32 flags, BaseThread* thread) { if (!mod || !op || !doc || !thread) return false; BaseList2D* t = GetBaseLink(mod, DescID(NEWSKINTAG), Tweights); BaseList2D* l = GetBaseLink(mod, DescID(NEWSKINCHECK), Opolygon); if (!t || !l) return false; PointObject* linkOp = static_cast<PointObject*>(l); CAWeightTag* tag = static_cast<CAWeightTag*>(t); lasttagdirty = tag->GetDirty(DIRTYFLAGS::DATA); cachedir = linkOp->GetDirty(DIRTYFLAGS::CACHE); PointObject* pObj = ToPoint(op); const Vector * pListR = pObj->GetPointR(); Vector * pListW = pObj->GetPointW(); const Int pcount = pObj->GetPointCount(); const Int jcount = tag->GetJointCount(); jointdir = 0; for (Int i = 0; i < jcount; i++) { BaseObject* joint = tag->GetJoint(i, doc); jointdir += joint->GetDirty(DIRTYFLAGS::MATRIX); } auto worker = [jcount, op_mg, &pListR, &pListW, &doc, &tag](maxon::Int i) { Vector temp; Vector pos = op_mg * pListR[i]; for (Int m = 0; m < jcount; m++) { Vector pos = op_mg * pListR[i]; BaseObject* joint = tag->GetJoint(m, doc); Float weight = tag->GetWeight(m, i); if (weight == 0.0) continue; Matrix cjmg = joint->GetMg(); JointRestState jrest = tag->GetJointRestState(m); Matrix jmg = jrest.m_bMg; Matrix jmi = jrest.m_bMi; temp += weight * cjmg * jmi * pos; } pListW[i] = ~op_mg * temp; }; maxon::ParallelFor::Dynamic(0, pcount, worker); // Notify Cinema about the internal data update. op->Message(MSG_UPDATE); return true; } Bool RegisterSkinModifier() { return RegisterObjectPlugin(ID_OBJECTDATA_SKINMODIFIER, "C++ oskinmodifier"_s, OBJECT_MODIFIER, SkinModifier::Alloc, "oskinmodifier"_s, nullptr, 0); }
Hi @FlavioDiniz, first of all, welcome in the plugincafe community!
Don't worry, since it's your first topic, I've set up your topic correctly. But please for your next topics, use the Q&A functionnaly, make sure to also read How to Post Questions.
With that's said you can find the manual about SetDParameter in the C++ docs which will be trigger each time a user changes a value in the description of your tag. And also the C++ manual about GetDDescription. Here a quick example of the implementation in python
extensions = ["exr","hdr","jpg","png"] # Called by C4D when loading description def GetDDescription(self, node, description, flags): data = node.GetDataInstance() if data is None: return False # Load the description if not description.LoadDescription(node.GetType()): return False # If there is no description for c4d.TESTSCALE, we create it. Otherwise we override it and set DESC_CYCLE with our extensions list. singleID = description.GetSingleDescID() paramID = c4d.DescID(c4d.TESTSCALE) if singleID is None or paramID.IsPartOf(singleID)[0]: bcParam = description.GetParameterI(c4d.TESTSCALE) items = c4d.BaseContainer() for i, extension in enumerate(self.extensions): items.SetString(i, extension) bcParam[c4d.DESC_CYCLE] = items return (True, flags | c4d.DESCFLAGS_DESC_LOADED) # Called when a parameter is Set def SetDParameter(self, node, id, t_data, flags): if not node: return # called when the filename is changed. We add the path to our list and we return c4d.DESCFLAGS_PARAM_SET to inform c4d the paramaters is just sets if id[0].id == c4d.STRINGTEST: self.extensions.append(t_data) return (True, flags | c4d.DESCFLAGS_PARAM_SET) return False
If you have any questions, please let me know! Cheers, Maxime.
Hi @Ashton_FCS_PluginDev, we are glad to see you back in the new plugincafe community! No worries at all since it's your first post here, but please make sure to read and apply the rules defined on the following topics:
Regarding your issue, you read m_spec, but you never check if there is a bitmashader in it or not. And in your case, there is no, so m_spec is set to None, and you are then trying to access value. So please always check for None. Then since you directly read the BITMAPSHADER_FILENAME parameter from this shader, please also consider to check the shader type you retrieve, maybe a user used a noise and not a bitmap.
m_spec = userMat[c4d.MATERIAL_REFLECTION_SHADER] if not m_spec or m_spec.CheckType(c4d.Xbitmap): continue
Finally, as you may already be aware, reflection gets multiple layers. You get some information in this thread, how to deal with it.
If you have any others question, please let me know! Cheers, Maxime.
Hi @mike, No worries at all since, but please make sure to read and apply the rules defined on the following topics:
Regarding your question, you can make use of the Shift Priority Tags. You can find more information on the priority in the c4d help.
But C4D also offer the Shift Priority Tag. Finally, note in some case it can be a limitation of the viewport which not refresh properly while data are correctly set. And you need to manually refresh the viewport.
Hope it solves your issue, if you have any question please let me know! Cheers, Maxime!
Hi @codysorgenfrey. First of all, I would like to present my apologies for the time your topic took to get at least a constructive answer.
Since matrix object is a bit special MoData are often out of data. It is not an issue for you, here is another solution. If you want to read the point position of a matrix, you should convert it into a polygonObject and read Dpoint. The con of that is you are losing the Modata (flags, etc, but deformer/effector are still working of course).
BaseObject* GetVirtualObjects (BaseObject* op, HierarchyHelp* hh) { // Create a Null master BaseObject* outObject = BaseObject::Alloc(Onull); if (!outObject) return nullptr; // Get the cache BaseObject* matrixObj = op->GetDown(); if (!matrixObj || !matrixObj->IsInstanceOf(1018545)) // 1018545 = Matrix Object return outObject; // Get the Matrix as a poly, it do not proper touch the Matrix Object, so you have to do it manually Bool dirty; BaseObject* clone = op->GetAndCheckHierarchyClone(hh, matrixObj, HIERARCHYCLONEFLAGS::ASPOLY, &dirty, nullptr, false); if(!dirty) return outObject; // Touch the original matrix in order to hide it in viewport matrixObj->Touch(); // Get ptCount and Point Array Int32 ptCount = ToPoly(clone)->GetPointCount() / 4; const Vector* padr = ToPoint(clone)->GetPointR(); Dpoint *d = (Dpoint *) padr; // iterate over the point and create null at the pt matrix maxon::BaseArray<Vector> pointList; for(Int32 x = 0; x < ptCount; x++) { BaseObject* n = BaseObject::Alloc(Onull); if(!n) return outObject; // Get the Correct World Matrix Matrix result(d[x].m.off + matrixObj->GetMl().off, d[x].m.sqmat.v1 + matrixObj->GetMl().sqmat.v1, d[x].m.sqmat.v2 + matrixObj->GetMl().sqmat.v2, d[x].m.sqmat.v3 + matrixObj->GetMl().sqmat.v3); n->SetMg(result); n->InsertUnder(outObject); pointList.Append(d[x].GetPos()); } ApplicationOutput("@", pointList.GetCount()); return outObject; }
Note in this example it only works with 1 matrice object, and as you can see you need to manually touch the object and I assume the result of GetAndCheckHierarchyClone is the result of the Matrix Object itself. If you have multiple objects and a matrice is within, it may be worth doing the cache system yourself, please take a look at BaseObject Manual - Generating for more information.
If you have any question, please let me know. Cheers, Maxime.
Hi, @knekke first of all welcome in the plugin cafe community!
No worries at all since it's your first post here, but please make sure to read and apply the rules defined on the following topics:
I've set up your topic correctly, by marking it as a question adding tags and move it to the correct category.
With that's said regarding your question, a doc variable is available, in the python_init.py (take care python_init.py is no more loaded in Cinema 4D R20). But as you already figured it out, this doc is more or less useless (it can be used to do some stuff), but since python_init.py is called very early, the document is replaced by the new.c4d file afterward. So any change made in the doc provided with python_init.py are lost.
But what you could do is write a PluginMessage plugin and react to C4DPL_PROGRAM_STARTED. Here a basic example which adds a cube to the scene. Create a *.pyp file in the plugin folder of cinema c4d and past the following code.
import c4d def PluginMessage(id, data): if id==c4d.C4DPL_PROGRAM_STARTED: doc = c4d.documents.GetActiveDocument() doc.InsertObject(c4d.BaseObject(c4d.Ocube))
Hi Thomas,
There is MSG_DOCUMENTINFO. And this message is available in python.
def Message(id, data): if id == c4d.MSG_DOCUMENTINFO: if not data: return True if data["type"] = c4d.MSG_DOCUMENTINFO_TYPE_LOAD: print "newDoc"
Unfortunately, there is no way to get this message called in C4D, since Python object which supports and receive a Message function are all scene dependants (like Tag, ObjectData). In C++ we do have the SceneHook NodeData plugin which allows this kind of stuff since it's a global NodeData, not attached to a special document.
So your idea is probably the best way to go if you stick to python. Cheers, Maxime.
Hi @Rage
I guess export_OBJ example on our Github repository is what you are looking for. Note for the moment values of the BaseContainer are not available in the Python Documentation. But you could find them in the C++ documentation about fobjexport2.h.
If you have any question please let me know. Cheers, Maxime.
Hi @pyr,
In Python, there is a GIL which do not allow Python by design to execute something in parallel.
In Cinema 4D all our functions (so everything which is in the c4d python module) is "GIL safe" in order to avoid any issue with Python memory. So in Cinema 4D thread are designed for doing GUI, or background stuff. Moreover, keep in mind creating a Thread have a cost in term of time (to create, execute, delete them).
Finally, I would like to point you to multiprocessing vs multi-threading.
Note that since it's more an algorithm problem than an issue related to our API, we can only provide you hints.
btw, I also turned your topic as a question. See Q&A New Functionality. Cheers, Maxime.
Hi @FlavioDiniz, as C4DS said the correct way to handle shortcut is to create a command for them.
Beside of that in this CommandData.Execute you could do two things:
With that's said, you could also listen for Message BFM_INPUT_DEVICE and especially BFM_INPUT_KEYBOARD. Then react to the keyboard, as it's explained in the Mouse and Keyboard Interaction C++ Manual. But in this case, it will not show up in the customize commands window.
If you have any question please let me know! Cheers, Maxime.
Hi @sungam, first of all, welcome in the plugincafe community and thanks a lot for your feedback. I know these words will not help you but we are currently working to improve our Python Documentation and examples as well.
With that's said, I know documentation should be sufficient, but MAXON offers free support for developers at plugincafe. So please contact us when you are in trouble, don't know where to look at or even get SDK issue.
res folder is a specific folder where resources needed for your plugin are stored. (e.g. you develop an ObjectData plugin(a new object, like a cube), this cube get a description (a set of parameters, which make a description of the actual object representation), so you need to define theses descriptions within the res folder. Same thing for string and so on...).
Clion is actually a C++ IDE, JetBrains offer Pycharm which is also supported by c4dpy. See C4DPY manual we cover how to setup Pycharm with c4dpy.
"EnvironmentError: cannot find pyp file - plugin registration failed". I'm not sure to understand this, this error comes from c4dpy? Is it possible to get your current plugin, at least the code of your pyp file?
If you have any questions, please open a new thread, in plugincafe we prefer to have one topic per thread so we can focus only into this specific issue and don't screw conversation, it's also easier for other peoples to find information.
Do not hesitate to explain to us your project, so we can guide you (help us, to help you ). If it's something you don't want to disclose you can always reach us at [email protected].
Hi @merkvilson, just a quick reminder I turned on your topic as a Question, Please See Q&A Functionnality.
Regarding your issue, C4D does not let you modify a Spline generator. As you may know, all spline generator hold a c4d.LineObject in their cache. And MCOMMAND_DELETE does fail on a LineObject, it only supports PolygonObject or SplineObject. So you first need to convert your spline generator as a SplineObject.
import c4d def MakeEditableClone(obj): # We clone the obj, since MakeEditable, modify the passed object and we don't want that. oDoc = obj.GetClone() doc = c4d.documents.BaseDocument() doc.InsertObject(oDoc) res = c4d.utils.SendModelingCommand( command = c4d.MCOMMAND_MAKEEDITABLE, list = [obj], mode = c4d.MODELINGCOMMANDMODE_ALL, doc = doc) if not res: return False return res[0] def Delete(obj): oDoc = obj.GetClone() if oDoc.GetDocument() is None: doc.InsertObject(oDoc) oDoc.GetPointS().SelectAll(1) res = c4d.utils.SendModelingCommand(command = c4d.MCOMMAND_DELETE, list = [oDoc], mode = 1, doc = doc, flags = 0) if res == True: retObj = oDoc.GetClone() oDoc.Remove() return retObj return False def main(): splineObj = MakeEditableClone(op) if not splineObj: return finalSpline = Delete(splineObj) if not finalSpline: return doc.InsertObject(finalSpline) c4d.EventAdd() # Execute main() if __name__=='__main__': main()
Hi Frank, you actually return True from the Message method while the result of the parent call was expected
def Message(self, msg, result): return c4d.gui.GeDialog.Message(self, msg, result)
For more information please read the documentation about GeDialog.Message.