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).
Software Engineer
Oh wait, I think I found a way. In case anybody else wants to know, here it is...
In the PrefsDialogObject:
Bool MyPrefsDialog::SetDParameter(GeListNode *node, const DescID &id,const GeData &t_data,DESCFLAGS_SET &flags) { BaseContainer* bc = MyPlugin::GetPreferences(); if (!bc) SUPER::SetDParameter(node, id, t_data, flags); switch (id[0].id) { // If PREFS_MYPLUGIN_SOMEVALUE was changed, store value and notify plugin objects in all open documents. case PREFS_MYPLUGIN_SOMEVALUE: bc->SetInt32(PREFS_MYPLUGIN_SOMEVALUE, t_data.GetInt32()); flags |= DESCFLAGS_SET::PARAM_SET; // Iterate open documents for (BaseDocument *doc = GetFirstDocument(); doc; doc = doc->GetNext()) { // Send broadcast message to each document, use unique ID doc->MultiMessage(MULTIMSG_ROUTE::BROADCAST, MyPlugin::UNIQUE_ID_PREFS, nullptr); } GeUpdateUI(); return true; } return SUPER::SetDParameter(node, id, t_data, flags); }
And then, in the plugin object:
Bool MyPluginObject::Message(GeListNode *node, Int32 type, void *data) { if (type == MyPlugin::UNIQUE_ID_PREFS) { GePrint("Aha! My prefs have changed!"_s); return true; } return SUPER::Message(node, type, data); }
And in deed, having the option of executing the source protector using a command line argument for Cinema 4D, or using c4dpy would be great for integration in a plugin build pipeline.
Hi, I'll just chime in here, as I'm involved in that project, too.
So the token hook needs to access certain elements of the scene to get their values, and the problem (thankfully) is easily reproducible.
I wrote a sample plugin that recreates the behaviour by simply returning data from the first object in the scene. Download it from our dropbox: tokenhookbug.zip
Here is the code:
This is the render setting in my example scene (which just contains a Cube):
Here is the render result in the Picture Viewer, notice the correct file name:
Uploading and rendering the file on TeamRender produces this:
The debugger clearly shows that doc->GetFirstObject() returns nullptr.
doc->GetFirstObject()
nullptr
Cheers, Frank
It works like a charm! Thank you again!
I was surprised at how little code was required.
Sharing is caring. In case anyone needs it, here's the code:
#include "ge_prepass.h" #include "c4d_general.h" #include "c4d_baselinkarray.h" #include "c4d_basedocument.h" /// /// \brief Registers observers and sends messages to them. /// class Observable { public: /// /// \brief Subscribes a new observer. /// /// \param[in] observer Pointer to an AtomGoal /// \param[in] doc The document that owns the AtomGoal /// /// \return A maxon error object if anything went wrong, otherwise maxon::OK /// maxon::Result<void> Subscribe(C4DAtomGoal *observer, BaseDocument *doc); /// /// \brief Unsubscribes an observer /// /// \param[in] observer Pointer to an AtomGoal that has previously been subscribed /// \param[in] doc The document that owns the AtomGoal /// void Unsubscribe(C4DAtomGoal *observer, BaseDocument *doc); /// /// \brief Sends a messages to all subscribed observers /// /// \param[in] type Message type /// \param[in] doc The document that owns the subscribed observers /// \param[in] data Optional message data /// void Message(Int32 type, BaseDocument *doc, void *data = nullptr) const; private: BaseLinkArray _observers; }; maxon::Result<void> Observable::Subscribe(C4DAtomGoal *observer, BaseDocument *doc) { if (!observer) return maxon::NullptrError(MAXON_SOURCE_LOCATION, "Observer must not be nullptr!"_s); // Check if this observer is already registered const Int32 observerIndex = _observers.Find(observer, doc); if (observerIndex != NOTOK) return maxon::OK; // Register new observer if (!_observers.Append(observer)) { return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION, "Failed to add observer to the list!"_s); } return maxon::OK; } void Observable::Unsubscribe(C4DAtomGoal *observer, BaseDocument *doc) { if (observer && doc) { const Int32 observerIndex = _observers.Find(observer, doc); if (observerIndex != NOTOK) { _observers.Remove(observerIndex); } } } void Observable::Message(Int32 type, BaseDocument *doc, void *data) const { for (Int32 i = 0; i < _observers.GetCount(); ++i) { C4DAtomGoal *atom = _observers.GetIndex(i, doc); if (atom) { atom->Message(type, data); } } }
Hi Adam, happy new year to you, too!
Since it works now, for some reason, I am pretty happy with what I have. However, since it might interest other plugin developers, I'll share more code. Maybe you have some tipps about improvements or potentially dangerous stuff, too.
The idea is that the shader has a LINK field where the user can link an object (which is also part of my plugin). The object can (but doesn't have to) provide a list of "custom outputs" that will be added to the shader's CYCLE. In the screenshot below it's the "Difference Map".
LINK
CYCLE
When a rendering is started, the shader will request the according data from the linked object during InitRender(). But that's not part of this thread
InitRender()
The shader's GetDDescription():
GetDDescription()
Bool TerrainOperatorShader::GetDDescription(GeListNode *node, Description *description, DESCFLAGS_DESC &flags) { iferr_scope_handler { GePrint(err.GetMessage()); return false; }; if (!description->LoadDescription(node->GetType())) return false; flags |= DESCFLAGS_DESC::LOADED; BaseDocument* doc = node->GetDocument(); const BaseContainer& dataRef = static_cast<BaseShader*>(node)->GetDataInstanceRef(); // Hide or show attributes, depending on shader mode const Bool slopeMode = dataRef.GetInt32(XTERRAINOPERATORSHADER_DATA) == XTERRAINOPERATORSHADER_DATA_SLOPE; TF4D::GUI::ShowDescription(node, description, XTERRAINOPERATORSHADER_SLOPE_DIRECTION_ENABLE, slopeMode); TF4D::GUI::ShowDescription(node, description, XTERRAINOPERATORSHADER_SLOPE_DIRECTION, slopeMode); // Get linked object BaseObject *linkedObject = dataRef.GetObjectLink(XTERRAINOPERATORSHADER_OPERATORLINK, doc); if (linkedObject) { // Get linked object's NodeData TF4D::BaseTerrainOperatorData* linkedOperator = linkedObject->GetNodeData<TF4D::BaseTerrainOperatorData>(); // Get list of custom outputs (these are the elements to add to the CYCLE) maxon::BaseArray<TF4D::GUI::CycleElementData> customOutputs; if (linkedOperator->GetCustomOperatorOutputs(customOutputs)) { if (!TF4D::GUI::AddCycleElements(node, description, XTERRAINOPERATORSHADER_DATA, customOutputs, true)) iferr_throw(maxon::UnexpectedError(MAXON_SOURCE_LOCATION, "Could not add LONG CYCLE elements!"_s)); } } return SUPER::GetDDescription(node, description, flags); }
The linked object's NodeData's GetCustomOperatorOutputs():
NodeData
GetCustomOperatorOutputs()
Bool ErosionOperator::GetCustomOperatorOutputs(maxon::BaseArray<TF4D::GUI::CycleElementData>& customOperatorOutputs) const { iferr_scope_handler { GePrint(err.GetMessage()); return false; }; customOperatorOutputs.Reset(); // GetCustomOutputName() simply returns a maxon::String customOperatorOutputs.Append(TF4D::GUI::CycleElementData(TF4D_CUSTOMOUTPUT_EROSION_DIFFERENCE, GetCustomOutputName(TF4D_CUSTOMOUTPUT_EROSION_DIFFERENCE))) iferr_return; return true; }
Ah, damn. Now I've spoiled that I'm working on erosion for Terraform4D
Why don't you avoid all those difficulties and simply implement that dialog using the C4D UI?
If you're storing data in the tag's BaseContainer, you don't need to override Write() and Read(). That's meant for private class member whose data would be lost otherwise.
Also, you can remove the "return true" at the end of the functions. As you already return in the previous line, it will never be called.
I did today, including it in projectdefinition.txt worked smoothly, thank you! If I ever find out why copying it do the folders does not work, I'll post it here.
Oh my god. the stupidest error.... OK, question 1 solved already, thanks
Ah, great, thank for checking! Then I don't need to worry about my bmp code
Ah, learned something, too
Hi Ferdinand,
thanks for your reply!
@ferdinand said in Compiling for R20... linker error in maxon::String:
Are you sure there are no build fragments in your build directory, so that the linker/compiler cannot get confused? Visual Studio has sometimes the tendency to make a mess. I have made a full rebuild, and cleaned the build, the .vs, and the generated folder before. Is there anything else? Visual Studio does tend to make a mess
Regenerating/checking your solution could also solve the problem. What do you mean? Deleting the solution file, and making a new one?
I wish I had tried R20 a bit more often. Last time is three months and hundreds of code changes ago
Hi,
I know the support for R20 has ended, but maybe someone still has an idea about this:
I'm building a project for R20 (I develop using mostly R25 for debugging), and it compiles without any warning or errors. However, linking fails with these errors:
error LNK2019: unresolved external symbol "class maxon::String __cdecl ToString(class String const &,class maxon::FormatStatement const *,bool)" ([email protected]@[email protected]@@[email protected]@[email protected][email protected]) referenced in function "void __cdecl maxon::ToStrHlp<class String>(class maxon::String &,class String const *,class maxon::FormatStatement const *)" ([email protected]@@@[email protected]@[email protected]@[email protected]@[email protected]@Z) error LNK2019: unresolved external symbol "class maxon::String __cdecl ToString(class Filename const &,class maxon::FormatStatement const *,bool)" ([email protected]@[email protected]@@[email protected]@[email protected]@[email protected]) referenced in function "void __cdecl maxon::ToStrHlp<class Filename>(class maxon::String &,class Filename const *,class maxon::FormatStatement const *)" ([email protected]@@@[email protected]@[email protected]@[email protected]@[email protected]@@Z)
Any idea where I should look to fix the error? Obviously, the problem has to do with some ToString() call. But I'm using these a lot throughout the code, and they when I last built for R20, there was no problem.
ToString()
Thanks in advance!
Frank
the SDK documentation says about GeClipMap::Init(BaseBitmap* bm):
GeClipMap::Init(BaseBitmap* bm)
Loads the clip map bitmap from bm. Any previous data is lost.
and about parameter bm:
bm
The bitmap to initialize the clip map with. The caller owns the pointed bitmap.
Sounds like it's copying the bitmap. Casting probably won't work, as GeClipMap and BaseBitmap are not related by inheritance. To get to the clipmap's bitmap, you need to use GeClipMap::GetBitmap().
GeClipMap
BaseBitmap
GeClipMap::GetBitmap()
Thanks, Paul! I'll try that out
You can download older SDK documentation versions here: https://developers.maxon.net/?page_id=1118
Thank you, that fixed it.
Interesting...
If I put all code aside, simply start Cinema 4D 2023, and do the following:
--> The cube does not change at all.
Is MCOMMAND_SUBDIVIDE maybe completely broken?
I have a function that looks like this:
Bool SubdividePolygonObject(PolygonObject* const objectPtr, const maxon::Int32 subdivisions, const maxon::Bool sds) { // Parameters for the modeling command BaseContainer modelingCommandParameters; modelingCommandParameters.SetBool(MDATA_SUBDIVIDE_HYPER, sds); modelingCommandParameters.SetInt32(MDATA_SUBDIVIDE_SUB, subdivisions); // Execute modeling command ModelingCommandData modelingCommandData; modelingCommandData.op = objectPtr; modelingCommandData.bc = &modelingCommandParameters; if (!SendModelingCommand(MCOMMAND_SUBDIVIDE, modelingCommandData)) return false; return true; }
Example usage:
BaseContainer cubeData; basicObjectData.SetVector(PRIM_CUBE_LEN, maxon::Vector(100.0)); cubeData.SetInt32(PRIM_CUBE_SUBX, 2); cubeData.SetInt32(PRIM_CUBE_SUBY, 2); cubeData.SetInt32(PRIM_CUBE_SUBZ, 2); PolygonObject* const objectPtr = static_cast<PolygonObject*>(GeneratePrimitive(nullptr, Ocube, cubeData, 1.0, false, hh->GetThread())); if (!SubdividePolygonObject(objectPtr, 5, true)) GePrint("Something went wrong."_s);
This works fine in R20 - R25, but returns false in 2023. What could be the reason?
false
Thanks, you just recieved it!