• CustomGUI gadget triggered by other gadget

    c++ r19 r20 r21
    10
    1
    0 Votes
    10 Posts
    1k Views
    C4DSC
    @m_magalhaes Thanks for your message-example. I hadn't quite understood it the first time, and needed a re-trigger. Inspired by your reply I came up with this solution instead. While it still is based on messaging, I preferred to use the SpecialEventAdd. This way, I could pass the type value via a "custom message". Obviously the customgui needs to be set up to react accordingly to this SpecialEventAdd, but this is what I sort of expected to already have been built in into the customgui, in order to be able to react to a trigger to update its additional attributes. Now we only can react to the attribute at constructor time. Maybe a request for a future update/addition? Note for others reading this topic: The code below is just a quick solution, having collected code from different sources and examples. It might obviously be optimized and commented in a better way. Also, make sure to register appropriate pluginIDs, as all ones used here are for demonstration purposes only. For the example below I followed a different route for testing purposes. Obviously it would lead me too far to provide a fully working plugin with NodeData etc ... As such, I wen for a simple CommandData with a GeDialog containing a set of radio buttons and a customgui gadget. The radio buttons allow to switch between black/white and r/g/b and c/m/y/k. With each selection the customgui displays a different set of options, showing a colored rectangle per option. The code is R20, but easily adjustable for R21. Main.cpp // ======================== // Cinema 4D C++ plugin // // PluginName: Test // Dummy "empty" plugin // ======================== // Main.cpp #include "c4d.h" // === Registered pluginIDs === #define MYCOMMAND_PLUGIN_ID 1000000 // DUMMY VALUE for demonstration purposes only !!! #define CUSTOMGUI_GADGET_ID 1000002 // DUMMY VALUE for demonstration purposes only !!! // a specific message ID to trigger the gadget to set its type #define CUSTOMGUI_GADGET_SETTYPEMSG_ID 100 // the gadget IDs #define RADIO_BUTTONS 10000 #define CUSTOMGUI_GADGET 10010 extern Bool RegisterGadget(); // ==================================== // GeDialog // ==================================== class MyDialog : public GeDialog { public: MyDialog(void) {} virtual ~MyDialog(void) {} virtual Bool CreateLayout(void); virtual Bool InitValues(void); virtual void DestroyWindow(void); virtual Bool Command(Int32 id, const BaseContainer& msg); virtual Int32 Message(const BaseContainer& msg, BaseContainer& result); }; Bool MyDialog::CreateLayout(void) { Bool res = GeDialog::CreateLayout(); // when using a GeLoadString(<string-id>) // strings need to be defined in the main string resources, not in the dialogs subfolder SetTitle("Test Dialog"_s); GroupBegin(0, BFH_SCALEFIT | BFV_SCALEFIT, 1, 0, ""_s, 0); { GroupBorderSpace(4, 4, 4, 4); GroupBegin(0, BFH_LEFT, 2, 0, maxon::String(), 0); { // 3 radio buttons vertically AddRadioGroup(RADIO_BUTTONS, BFV_SCALEFIT, 0, 3); AddChild(RADIO_BUTTONS, 2, "BW"_s); AddChild(RADIO_BUTTONS, 3, "RGB"_s); AddChild(RADIO_BUTTONS, 4, "CMYK"_s); // the custom gadget BaseContainer bc; AddCustomGui(CUSTOMGUI_GADGET, CUSTOMGUI_GADGET_ID, String(), 0, 0, 0, bc); } GroupEnd(); } GroupEnd(); return res; } Bool MyDialog::InitValues(void) { // first call the parent instance if (!GeDialog::InitValues()) return false; // do our thing ... Int32 typevalue = 2; SetInt32(RADIO_BUTTONS, typevalue); // set default to black and white // and inform the gadget about it SpecialEventAdd(CUSTOMGUI_GADGET_ID, CUSTOMGUI_GADGET_SETTYPEMSG_ID, typevalue); return true; } void MyDialog::DestroyWindow(void) {} Bool MyDialog::Command(Int32 id, const BaseContainer& msg) { if (id == RADIO_BUTTONS) { Int32 typevalue; GetInt32(RADIO_BUTTONS, typevalue); // send this to the customgui gadget to update its representation, // using an EventAdd will trigger a "regular" EVMSG_CHANGE // but we prefer to provide a SpecialEventAdd, as such we: // 1. avoid that the gadget is triggered by every EVMSG_CHANGE // 2. allow to specify a specific value to change the type to //EventAdd(); SpecialEventAdd(CUSTOMGUI_GADGET_ID, CUSTOMGUI_GADGET_SETTYPEMSG_ID, typevalue); } return true; } Int32 MyDialog::Message(const BaseContainer& msg, BaseContainer& result) { return GeDialog::Message(msg, result); } // ==================================== // CommandData // ==================================== class MyCommand : public CommandData { INSTANCEOF(MyCommand, CommandData) public: MyDialog dlg; public: virtual Bool Execute(BaseDocument* doc); }; Bool MyCommand::Execute(BaseDocument* doc) { if (dlg.IsOpen() == false) dlg.Open(DLG_TYPE::ASYNC, MYCOMMAND_PLUGIN_ID, -1, -1, 300, 200, 0); return true; } Bool RegisterMyCommand(void) { return RegisterCommandPlugin(MYCOMMAND_PLUGIN_ID, "Test"_s, 0, AutoBitmap("icon.png"_s), "Test"_s, NewObjClear(MyCommand)); } // ==================================== // Plugin Main // ==================================== Bool PluginStart(void) { ApplicationOutput("Test"_s); RegisterMyCommand(); RegisterGadget(); return true; } void PluginEnd(void) { } Bool PluginMessage(Int32 id, void * data) { switch (id) { case C4DPL_INIT_SYS: if (!g_resource.Init()) return false; return true; case C4DMSG_PRIORITY: return true; case C4DPL_BUILDMENU: break; case C4DPL_ENDACTIVITY: return true; } return false; } CustomGUI_Gadget.cpp // CustomGUI_Gadget.cpp // The custom gadget is a sort of horizontal graphical radio button group // the number of buttons is dependent the gadget type // type = 0 -> not initialized, no buttons // type = 2 has 2 buttons (black and white) // type = 3 has 3 buttons (red, green, blue) // type = 4 has 4 buttons (cyan, magenta, yellow, black) #include "c4d.h" #include "lib_clipmap.h" const Int32 kItemSize = 30; // the size of each "button" // === Registered pluginIDs === #define CUSTOMGUI_GADGET_ID 1000002 // DUMMY VALUE for demonstration purposes only !!! #define CUSTOMGUI_GADGET_ATTRIBUTE_ID 1000003 // DUMMY VALUE for demonstration purposes only !!! #define USERAREA_ID 10001 // The ID of the UserArea GUI element. // a specific message ID to trigger the gadget to set its type #define CUSTOMGUI_GADGET_SETTYPEMSG_ID 100 //--------------------------- // The user area used to display the custom datatype //--------------------------- class GadgetUserArea : public GeUserArea { public: GadgetUserArea(); virtual ~GadgetUserArea(); virtual Bool Init(); virtual Bool InitValues(); virtual Bool GetMinSize(Int32& w, Int32& h); virtual void DrawMsg(Int32 x1, Int32 y1, Int32 x2, Int32 y2, const BaseContainer& msg); virtual Bool InputEvent(const BaseContainer& msg); Int32 mSelection; Int32 mType; Vector bw[2]; Vector rgb[3]; Vector cmyk[4]; // use a GeClipMap for drawing AutoAlloc<GeClipMap> mClipmap; }; GadgetUserArea::GadgetUserArea() { mSelection = 0; mType = 0; } GadgetUserArea::~GadgetUserArea() { } Bool GadgetUserArea::Init() { bw[0] = Vector(0); bw[1] = Vector(255); rgb[0] = Vector(255, 0, 0); rgb[1] = Vector(0, 255, 0); rgb[2] = Vector(0, 0, 255); cmyk[0] = Vector(0, 255, 255); cmyk[1] = Vector(255, 0, 255); cmyk[2] = Vector(255, 255, 0); cmyk[3] = Vector(0); return true; } Bool GadgetUserArea::InitValues() { return true; } Bool GadgetUserArea::GetMinSize(Int32& w, Int32& h) { w = kItemSize * 4; h = kItemSize; return true; } void GadgetUserArea::DrawMsg(Int32 x1, Int32 y1, Int32 x2, Int32 y2, const BaseContainer& msg) { OffScreenOn(); if (!mClipmap) return; const Int32 w = GetWidth(); const Int32 h = GetHeight(); mClipmap->Init(w, h, 32); mClipmap->BeginDraw(); // background Int32 r, g, b; GetColorRGB(COLOR_BG, r, g, b); mClipmap->SetColor(r, g, b, 255); mClipmap->FillRect(x1, y1, x2, y2); Vector color; if (mType != 0) { // draw the possible options as background, // then draw the current selected option on top switch (mType) { case 2: // black and white { for (Int32 col = 0; col < 2; ++col) { color = bw[col]; mClipmap->SetColor(SAFEINT32(color.x), SAFEINT32(color.y), SAFEINT32(color.z), 255); Int32 x = col * kItemSize; mClipmap->FillRect(x + 2, 2, x + kItemSize - 2, kItemSize - 2); } break; } case 3: // RGB { for (Int32 col = 0; col < 3; ++col) { color = rgb[col]; mClipmap->SetColor(SAFEINT32(color.x), SAFEINT32(color.y), SAFEINT32(color.z), 255); Int32 x = col * kItemSize; mClipmap->FillRect(x + 2, 2, x + kItemSize - 2, kItemSize - 2); } break; } case 4: // CMYK { for (Int32 col = 0; col < 4; ++col) { color = cmyk[col]; mClipmap->SetColor(SAFEINT32(color.x), SAFEINT32(color.y), SAFEINT32(color.z), 255); Int32 x = col * kItemSize; mClipmap->FillRect(x + 2, 2, x + kItemSize - 2, kItemSize - 2); } break; } } if (mSelection >= 0) { // orange "selection" color GetColorRGB(COLOR_TEXTFOCUS, r, g, b); mClipmap->SetColor(r, g, b, 255); Int32 x = mSelection * kItemSize; mClipmap->Rect(x, 0, x + kItemSize, kItemSize); mClipmap->Rect(x + 1, 1, x + kItemSize - 1, kItemSize - 1); } } mClipmap->EndDraw(); DrawBitmap(mClipmap->GetBitmap(), 0, 0, w, h, 0, 0, w, h, BMP_ALLOWALPHA); } Bool GadgetUserArea::InputEvent(const BaseContainer& msg) { // check the input device switch (msg.GetInt32(BFM_INPUT_DEVICE)) { // some mouse interaction case BFM_INPUT_MOUSE: { // get the cursor position Int32 mx = msg.GetInt32(BFM_INPUT_X); Int32 my = msg.GetInt32(BFM_INPUT_Y); Global2Local(&mx, &my); // Note that the origin of a GeUserArea is upperleft // (which is equal to the 4th quadrant of cartesian coordinate system) mSelection = mx / kItemSize; // inform the parent that the data has changed BaseContainer m(BFM_ACTION); m.SetInt32(BFM_ACTION_ID, GetId()); m.SetData(BFM_ACTION_VALUE, mSelection); SendParentMessage(m); //Redraw(); return true; } } return false; } //---------------------------------------------------------------------------------------- // A custom GUI to display the ReferencePoint //---------------------------------------------------------------------------------------- class iGadget : public iCustomGui { INSTANCEOF(iGadget, iCustomGui) private: // The current tristate. Bool mTristate; // instance of the userarea to display the ReferencePointer GadgetUserArea mUA; public: iGadget(const BaseContainer &settings, CUSTOMGUIPLUGIN *plugin); virtual Bool CreateLayout(); virtual Bool InitValues(); virtual Bool Command(Int32 id, const BaseContainer &msg); virtual Int32 Message(const BaseContainer &msg, BaseContainer &result); virtual Bool SetData(const TriState<GeData> &tristate); virtual TriState<GeData> GetData(); virtual void CustomGuiRedraw(); }; iGadget::iGadget(const BaseContainer &settings, CUSTOMGUIPLUGIN *plugin) : iCustomGui(settings, plugin) { mUA.mType = settings.GetInt32(CUSTOMGUI_GADGET_ATTRIBUTE_ID); //mUA.mType = mType; // pass along the type to the userarea // Defining default values mTristate = false; }; Bool iGadget::CreateLayout() { GroupBegin(1000, BFH_SCALEFIT | BFV_FIT, 1, 1, String(), 0); { GroupSpace(0, 0); // Attach the User Area to the custom GUI AddUserArea(USERAREA_ID, BFH_SCALEFIT, 0, 0); AttachUserArea(mUA, USERAREA_ID); } GroupEnd(); return SUPER::CreateLayout(); }; Bool iGadget::InitValues() { // The data and it's tristate are handled automatically. this->SetInt32(USERAREA_ID, mUA.mSelection, mTristate); return true; }; Bool iGadget::Command(Int32 id, const BaseContainer &msg) { switch (id) { case USERAREA_ID: { // The Gadget was changed. // Update GUI this->InitValues(); // Send message to parent object to update the parameter value. BaseContainer m(BFM_ACTION); m.SetInt32(BFM_ACTION_ID, GetId()); m.SetData(BFM_ACTION_VALUE, msg.GetInt32(BFM_ACTION_VALUE)); SendParentMessage(m); return true; break; } } return SUPER::Command(id, msg); } Int32 iGadget::Message(const BaseContainer &msg, BaseContainer &result) { switch (msg.GetId()) { case BFM_CORE_MESSAGE: { if (!CheckCoreMessage(msg)) { break; } else { if (msg.GetInt32(BFM_CORE_ID) == EVMSG_CHANGE) { // get the parameter and update the userarea if needed //ApplicationOutput("iGadget::Message() detected EVMSG_CHANGE"); } if (msg.GetInt32(BFM_CORE_ID) == CUSTOMGUI_GADGET_ID) { UInt par1 = (UInt)msg.GetVoid(BFM_CORE_PAR1); UInt par2 = (UInt)msg.GetVoid(BFM_CORE_PAR2); //ApplicationOutput("iGadget::Message() detected a SpecialEventAdd @ @", par1, par2); if (par1 == CUSTOMGUI_GADGET_SETTYPEMSG_ID) { // The SpecialEventAdd which is responsable for this message // uses the CUSTOMGUI_GADGET_ID as message ID, // a specific value CUSTOMGUI_GADGET_SETTYPEMSG_ID as first parameter to indicate we want to set the gadget's type, // and finally the type value as the second parameter // -> SpecialEventAdd(CUSTOMGUI_GADGET_ID, CUSTOMGUI_GADGET_SETTYPEMSG_ID, typevalue); // accept the type change for the gadget and trigger an update // (see iGadget::Command, where user interaction in the userarea triggers an update) mUA.mType = (Int32)par2; // reset the selection to avoid "out of range" depending the type mUA.mSelection = 0; CustomGuiRedraw(); } } } break; } } return SUPER::Message(msg, result); } Bool iGadget::SetData(const TriState<GeData> &tristate) { // The data is changed from the outside. mUA.mSelection = tristate.GetValue().GetInt32(); mTristate = tristate.GetTri(); this->InitValues(); // need to update the userarea this->mUA.Redraw(); return true; }; TriState<GeData> iGadget::GetData() { // The data is requested from the outside. TriState<GeData> tri; tri.Add(mUA.mSelection); return tri; }; void iGadget::CustomGuiRedraw() { this->mUA.Redraw(); } //---------------------------------------------------------------------------------------- // This CustomGuiData class registers a new custom GUI for the ReferencePoint datatype. //---------------------------------------------------------------------------------------- class Gadget : public CustomGuiData { public: virtual Int32 GetId(); virtual CDialog* Alloc(const BaseContainer& settings); virtual void Free(CDialog* dlg, void* userdata); virtual const Char* GetResourceSym(); virtual CustomProperty* GetProperties(); virtual Int32 GetResourceDataType(Int32*& table); }; static Int32 g_stringtable[] = { DTYPE_LONG }; //< This array defines the applicable datatypes. static CustomProperty g_GadgetType[] = { { CUSTOMTYPE::LONG, CUSTOMGUI_GADGET_ATTRIBUTE_ID, "TYPE" }, { CUSTOMTYPE::END, 0, "" } }; Int32 Gadget::GetId() { return CUSTOMGUI_GADGET_ID; }; CDialog* Gadget::Alloc(const BaseContainer& settings) { // Creates and returns a new sub-dialog. iGadget* dlg = NewObjClear(iGadget, settings, GetPlugin()); if (!dlg) return nullptr; CDialog *cdlg = dlg->Get(); if (!cdlg) return nullptr; return cdlg; }; void Gadget::Free(CDialog* dlg, void* userdata) { // Destroys the given subdialog. if (!dlg || !userdata) return; iGadget* sub = static_cast<iGadget*>(userdata); DeleteObj(sub); }; const Char* Gadget::GetResourceSym() { // Returns the resource symbol. This symbol can be used in resource files in combination with "CUSTOMGUI". return "TYPE"; }; CustomProperty* Gadget::GetProperties() { // This method can return a pointer to a data structure holding various additional properties. return g_GadgetType; }; Int32 Gadget::GetResourceDataType(Int32*& table) { // Returns the applicable datatypes defined in the stringtable array. table = g_stringtable; return sizeof(g_stringtable) / sizeof(Int32); }; Bool RegisterGadget() { // only register the custom GUI when not already registered by another plugin if (IsLibraryInstalled(CUSTOMGUI_GADGET_ID)) return true; static BaseCustomGuiLib myGadgetLib; ClearMem(&myGadgetLib, sizeof(myGadgetLib)); FillBaseCustomGui(myGadgetLib); if (!InstallLibrary(CUSTOMGUI_GADGET_ID, &myGadgetLib, 1000, sizeof(myGadgetLib))) return false; if (!RegisterCustomGuiPlugin(/*GeLoadString(IDS_CUSTOMGUISTRING)*/"Gadget"_s, 0, NewObjClear(Gadget))) return false; return true; } With this I guess the topic can be closed. But feel free to provide further comments if I overlooked something.
  • What's wrong with GetSelection()?

    r21 python
    7
    0 Votes
    7 Posts
    827 Views
    M
    Hi @Cairyn thanks for spotting it out, it's an issue in the python documentation, if you take a look at the C++ documentation it's said that it supports only Objects and Tags (see BaseDocument Selections Manual). (I will fix the Python doc) On other hand, I confirm to deselect all objects/tags the best way is to call SetSelection with None/nullptr. And finally, as zipit said, in the end, it's only a wrapper around BIT_ACTIVE with the benefic of GetSelection, that the selection is cached so you avoid the iteration on the whole scene. Cheers, Maxime.
  • VolumeObject ReadObject and WriteObject no supported?

    c++ r20
    3
    0 Votes
    3 Posts
    465 Views
    kbarK
    Thanks Riccardo.
  • CurrentStateToObject returning Null

    10
    0 Votes
    10 Posts
    1k Views
    ManuelM
    hello @rui_mac without futher information i'll change this thread as resolved tomorrow.
  • Use existing data for DynamicDescription

    c++ windows r21
    3
    0 Votes
    3 Posts
    660 Views
    O
    Thanks @s_bach This is very helpful. Best wishes.
  • Python Tag: Matrix is always dirty? (or vice versa)

    sdk python
    9
    0 Votes
    9 Posts
    1k Views
    M
    Hi @orestiskon I'm afraid there is nothing much more to say except what @zipit said. Just a few notes: IsDirty is build to be used within a generator to check the current object only. GetDirty retrieves an integer value ta represents the dirty state of an object. It can be used to retrieve DirtyCount from outside. Now take into consideration that the matrix object is kind of special since in reality, it creates nothing. But only feed some MoData and display them (but create no geometry). So how does an object that creates nothing can have its cache dirty? That's why it's up to the object that modifies the MoData (stored in its hidden tag ID_MOTAGDATA) to tell the matrix its content is dirty so other people that rely on this matrix know about it. Additionally to what @zipit said (which will work in any case and it's preferred) You can also consider checking for the dirtiness of the linked effector (but this will not consider Field). Here an example in a Python Generator import c4d def CheckDirtyObj(obj, uuid, flag): """ Checks if an object by comparing the current Dirt Value with the one stored in the current op.BaseContainer :param obj: The BaseList2D to retrieve the dirty state from. :param uuid: The uuid used to store in the BaseContainer. :param flag: The dirtiness flag to check for. :return: True if the object is dirty, False otherwise. """ def GetBc(): """ Retrieves a BC stored in the object BC, or create it if it does not exist yet :return: A BaseContainer where value can be stored. """ bcId = 100001 # Make sure to obtain an UNIQUE ID in plugincafe.com bc = op.GetDataInstance().GetContainerInstance(bcId) if bc is None: op.GetDataInstance().SetContainer(bcId, c4d.BaseContainer()) bc = op.GetDataInstance().GetContainerInstance(bcId) if bc is None: raise RuntimeError("Unable to create BaseContainer") return bc # Retrieves the stored value and the true DirtyCount storedDirtyCount = GetBc().GetInt32(uuid, -1) dirtyCount = obj.GetDirty(flag) # Compares them, update stored value and return if storedDirtyCount != dirtyCount: GetBc().SetInt32(uuid, dirtyCount) return True return False def main(): # Retrieve attached object and check if it's a matrix object matrixObj = op[c4d.ID_USERDATA, 1] if matrixObj is None or not matrixObj.CheckType(1018545): return c4d.BaseObject(c4d.Onull) # Retrieve the current cache opCache = op.GetCache() # We need a new object if one of the next reason are False # The Cache is not valid # The Parameter or Matrix of the current generator changed # The Parameter or Matrix of the linked Matrix changed needNewObj = opCache is None needNewObj |= not opCache.IsAlive() needNewObj |= op.IsDirty(c4d.DIRTYFLAGS_DATA | c4d.DIRTYFLAGS_MATRIX) needNewObj |= CheckDirtyObj(matrixObj, 0, c4d.DIRTYFLAGS_DATA | c4d.DIRTYFLAGS_MATRIX) # The Parameter or Matrix of effectors of the linked Matrix changed objList = matrixObj[c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST] for objIndex in xrange(objList.GetObjectCount()): # If the effector is disabled in the effector list, skip it if not objList.GetFlags(objIndex): continue # If the effector is not valid or not enabled, skip it obj = objList.ObjectFromIndex(op.GetDocument(), objIndex) if obj is None or not obj.IsAlive() or not obj.GetDeformMode(): continue # Check for the dirty value stored (+1 because we already used ID 0 for the matrix object) needNewObj |= CheckDirtyObj(obj, objIndex + 1, c4d.DIRTYFLAGS_DATA | c4d.DIRTYFLAGS_MATRIX) if not needNewObj: print "Old Obj" return opCache print "Generated New Object" return c4d.BaseObject(c4d.Ocube) Cheers, Maxime.
  • Asset Manager

    3
    0 Votes
    3 Posts
    598 Views
    S
    Update: the previous version of the code had some ill-formatted statements. You can actually define the scheme as part of the Url, without setting it using SetScheme(): const maxon::Url myUrl { "myassets:///test.txt"_s }; Thus, the path is resolved just by adding the path to the target folder: maxon::String resolvedPath { "file:///c:/assets/" }; resolvedPath.Append(_url.GetPath()) iferr_return; best wishes, Sebastian
  • Vertex Weight Issue

    r21
    3
    0 Votes
    3 Posts
    539 Views
    O
    Did it as you said. They've just answered. It's a know bug they said. Thanks.
  • Mirroring a Pose

    python
    3
    1
    0 Votes
    3 Posts
    452 Views
    ?
    Thank you for the clarification, @zipit.
  • Dirty State with Python Generator

    python sdk
    5
    1 Votes
    5 Posts
    1k Views
    orestiskonO
    Thanks a lot for the explanation! I replaced the plugin id with a user data and it seems to work ok. Still trying to figure out the matrix on the other thread though. Here is the adapted code: def main(): ID_PYGEN_DIRTY_CACHE = op[c4d.ID_USERDATA,3] linked_object = op[c4d.ID_USERDATA, 2] # Get the last cached dirty count and the current dirty count. lst_dcount = ID_PYGEN_DIRTY_CACHE cur_dcount = linked_object.GetDirty(c4d.DIRTYFLAGS_ALL) if linked_object is None: return c4d.BaseList2D(c4d.Onull) # Return the linked object if its dirty count exceeds our cached dirty # count or there is no cached dirty count. if lst_dcount is None or lst_dcount < cur_dcount or op.GetCache() is None: # Cache the current dirty count. op[c4d.ID_USERDATA,3] = cur_dcount clone = linked_object.GetClone(c4d.COPYFLAGS_NO_HIERARCHY) clone.SetMg(c4d.Matrix()) print "Built cache" return clone # Otherwise return the cache. else: print "Returned cache" return op.GetCache() And the file: python_dirtyState_0001.c4d
  • Accessing a virtual matrix object.

    c++ sdk
    5
    0 Votes
    5 Posts
    940 Views
    J
    @m_magalhaes Thanks for the response Manuel, that was exactly what I needed. John Thomas
  • First R21 plugin ... memory leaks and dangling references

    r21 c++
    13
    0 Votes
    13 Posts
    2k Views
    C4DSC
    @r_gigante Thanks for the feedback. FYI, this wasn't only with R21, had the same issue with R20, since the "old laptop" was restricted to R20 only. But I guess no updates will follow for R20 anymore.
  • how to get all subobject(children) of active object?

    python r21
    6
    1
    0 Votes
    6 Posts
    2k Views
    r_giganteR
    Hi @gheyret, thanks for reaching out us. Although @zipit has already provide an exhaustive answer to the topic (thanks man!) , I warmly suggest to look also to this blog post[URL-REMOVED] from @fwilleke80 and also to have a look at Navigation in Lists and Trees section on the GeListNode Manual. Best, Riccardo [URL-REMOVED] @maxon: This section contained a non-resolving link which has been removed.
  • Where Can I Find Documentation for Licensing a Python Plugin?

    python
    6
    1
    0 Votes
    6 Posts
    999 Views
    ?
    @kbar Thank you for your response. That's really helpful, thank you.
  • This topic is deleted!

    1
    1
    0 Votes
    1 Posts
    4 Views
    No one has replied
  • How to handle C4D Unicode in Python scripting?

    r21 python windows
    12
    0 Votes
    12 Posts
    2k Views
    CairynC
    @zipit said in How to handle C4D Unicode in Python scripting?: well, that API object names thing is a flaw of Python 2. So working as expected or not as expected is a bit a question of the point of view. If you got the string passed from any other source the problem would be the same. Right. The main thing is to understand the issue, and then to write the chapter in a way that explains what to watch out for. (I do wonder how third-party modules would do with a name string passed to them from a script that reads them from the API... well, another bridge to cross another day.) Python 3 clearly is superior in that respect, as there is no unicode class and all str objects are unicode (what they appear to be already in C4D, but with matching len, index, and slice capabilities). On a more productive note: I think that focusing in Unicode strings isn't really that important for Python stuff in c4d, since object names should be something you largely ignore as they are a unreliable source of identification and only are rarely important in other contexts. Hmm, I am not sure whether I would agree to that. Good naming is essential to find your way through complex scenes, and a good naming schema can be built in a way that is friendly to string search and comparison criteria, esp. if you can build your own scripts to perform the search and selection. I just point at the _L _R naming schema for joints that is common in C4D's docs. Of course, if your objects are all named Cube, Cube.1, Cube.2, Cube.3, then name-based identification may be unhelpful Anyway, I am not the person to judge that, as I am only teaching Python to interested users. What they do with it is their own decision; I just have to point out the crucial points so they can apply the code to their own concepts.
  • Preventing SetDocumentData from switching to active tool

    python r20 windows
    5
    0 Votes
    5 Posts
    775 Views
    B
    I'm just going to go ahead and mark this as solved. Either using an empty Basecontainer or the set function solve the problem. Thank you for explaining!
  • Howto add a Field input to a python plugin

    python r19 r20 r21
    3
    0 Votes
    3 Posts
    583 Views
    P
    @s_bach said in Howto add a Field input to a python plugin: FIELDLIST works fine - i think i got an strange error before and trying CUSTOMFIELDLIST etc. solved
  • LocalDateTime and UniversalDateTime questions

    r21 c++ maxon api
    5
    0 Votes
    5 Posts
    610 Views
    ferdinandF
    Hi, well, aside from the "at least I am done with it"-approach of just adding 24 hours to each license, there is the static method UniversalDateTime::FromValues() which should be close enough to a FromString(). There is also UniversalDateTime::GetNow(). But the description on that is a bit ambiguous. It says "Return[s] the current date-time object of the current time zone.", while the UniversalDateTime class description says "Class that represents the Universal date-time (UTC+0000)". Unless I am overlooking something here, I would say both cannot be true at the same time, and would expect UniversalDateTime::GetNow() to actually return the current time in UTC +0 (as a UniversalDateTime). The problem with DST is, that it is not anything truly predictable. While your problem could be rooted in a bug, there is a rich history of big organisations struggling with DST (Microsoft, Apple, etc.) and using DST makes you dependent on them maintaining their OS/services properly. On a more light-hearted note: When dealing with standardisation of human conventions I always have to think of this XKCD [image: unicode.png] Cheers zipit
  • BFM_INPUT_DOUBLECLICK in GeUserArea...

    c++ classic api macos
    7
    0 Votes
    7 Posts
    810 Views
    fwilleke80F
    OK, thank you!