Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login
    1. Maxon Developers Forum
    2. fwilleke80
    3. Best
    • Profile
    • Following 0
    • Followers 0
    • Topics 103
    • Posts 450
    • Best 28
    • Controversial 0
    • Groups 0

    Best posts made by fwilleke80

    • RE: Best way to update objects after preference change?

      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);
      }
      
      posted in Cinema 4D SDK
      fwilleke80F
      fwilleke80
    • RE: Python Source Protector: Can it be called via CLI?

      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.

      posted in Cinema 4D SDK
      fwilleke80F
      fwilleke80
    • RE: Custom Tokens with Team Render Server

      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:
      tokenhookbug_code.png

      This is the render setting in my example scene (which just contains a Cube):
      tokenhookbug_rendersetting.png

      Here is the render result in the Picture Viewer, notice the correct file name:
      tokenhookbug_result_pv.png

      Uploading and rendering the file on TeamRender produces this:
      tokenhookbug_result_tr.png

      The debugger clearly shows that doc->GetFirstObject() returns nullptr.

      Cheers,
      Frank

      posted in Cinema 4D SDK
      fwilleke80F
      fwilleke80
    • RE: Shader that gets data from an object: Refresh

      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);
      		}
      	}
      }
      
      posted in Cinema 4D SDK
      fwilleke80F
      fwilleke80
    • RE: Dynamic elements in a CYCLE, CYCLE empty after loading document?

      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".

      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 😉

      Screenshot 2021-01-26 at 11.42.45.png

      The shader's 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():

      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;
      }
      

      Cheers,
      Frank

      Ah, damn. Now I've spoiled that I'm working on erosion for Terraform4D 😄

      posted in Cinema 4D SDK
      fwilleke80F
      fwilleke80
    • RE: PySide2 Integration

      Why don't you avoid all those difficulties and simply implement that dialog using the C4D UI?

      posted in General Talk
      fwilleke80F
      fwilleke80
    • RE: Save node data in Read and Write

      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.

      posted in Cinema 4D SDK
      fwilleke80F
      fwilleke80
    • RE: Type Viewer not working in Visual Studio 2015

      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.

      posted in Cinema 4D SDK
      fwilleke80F
      fwilleke80
    • RE: Threading & job questions

      Oh my god. the stupidest error.... 😵
      OK, question 1 solved already, thanks 😁

      posted in Cinema 4D SDK
      fwilleke80F
      fwilleke80
    • RE: Creating and saving a 32bit grayscale bitmap

      Ah, great, thank for checking!
      Then I don't need to worry about my bmp code 🙂

      Cheers,
      Frank

      posted in Cinema 4D SDK
      fwilleke80F
      fwilleke80
    • RE: Python Source Protector: Can it be called via CLI?

      Sounds like a good solution to me, too!
      And I second Kent's question about the compatibility of encryptet pyp files.

      posted in Cinema 4D SDK
      fwilleke80F
      fwilleke80
    • RE: Overlaying icons (like Alembic)

      OK, I got it. Once it's all figured out, the solution seems laughably simple.

      In case anybody else needs to maintain R20 compatibility and wants to use custom icon overlays, here's a solution:

      In MyObject class declaration:
      (R20 code)

      class MyObject : public ObjectData
      {
      	INSTANCEOF(MyObject, ObjectData);
      
      	// ...
      
      private:
      #if API_VERSION < 21000
      	BaseBitmap *_customIcon; ///< Used to store a node's custom icon in R20
      #endif
      };
      
      public:
      #if API_VERSION < 21000
      	MyObject() : _customIcon(nullptr)
      	{
      	}
      
      	~ MyObject()
      	{
      		if (_customIcon)
      			BaseBitmap::Free(_customIcon);
      	}
      #endif
      

      In MyObject::Message():
      (R20 code)

      const BaseContainer &dataRef = op->GetDataInstanceRef();
      
      //
      // 1. Copy default icon into customIcon
      //
      
      // Load default icon
      IconData defaultIconData;
      if (!GetIcon(node->GetType(), &defaultIconData))
      	return false;
      
      // Free _customIcon if it has been allocated before, because
      // it will now receive the pointer to a newly allocated BaseBitmap.
      if (_customIcon)
      	BaseBitmap::Free(_customIcon);
      
      // Get the actual bitmap that is our icon
      _customIcon = defaultIconData.GetClonePart();
      if (!_customIcon)
      	return false;
      
      //
      // 2. Blit overlay into customIcon
      //
      
      // Load overlay icon
      IconData overlayIcon;
      const Int32 overlayIconId = MyFunctionToGetTheIconId();
      if (overlayIconId == NOTOK)
      	return false;
      const Bool overlayIconloaded = GetIcon(overlayIconId, &overlayIcon);
      if (overlayIconloaded)
      {
      	BlitOverlayBitmap(overlayIcon.bmp, _customIcon, overlayIcon.x, overlayIcon.y, overlayIcon.w, overlayIcon.h, 32, 32);
      
      	//
      	// 3. Set cid->dat to use customIcon
      	//
      	cid->dat->bmp = _customIcon;
      	cid->dat->x = 0;
      	cid->dat->y = 0;
      	cid->dat->w = _customIcon->GetBw();
      	cid->dat->h = _customIcon->GetBh();
      	cid->filled = true;
      }
      

      And BlitOverlayBitmap(), a variation of your BlitOverlayIcon():

      void BlitOverlayBitmap(BaseBitmap *overlay, BaseBitmap *dest, Int32 x, Int32 y, Int32 bw, Int32 bh, Int32 xOffset, Int32 yOffset)
      {
      	if (!overlay || !dest)
      		return;
      
      	BaseBitmap *overlayAlpha = overlay->GetInternalChannel();
      	BaseBitmap *destAlpha = dest->GetInternalChannel();
      
      	const Int32 destW = dest->GetBw();
      	const Int32 destH = dest->GetBh();
      	if (bw > destW)
      		bw = destW;
      	if (bh + yOffset > destH)
      		bh = destH - yOffset;
      	if (bw + xOffset > destW)
      		bw = destW - xOffset;
      
      	UInt16 aa, a, rr, r, gg, g, bb, b;
      	aa = a = rr = r = gg = g = bb = b = 0xff;
      
      	for (Int32 py = 0; py < bh; py++)
      	{
      		for (Int32 px = 0; px < bw; px++)
      		{
      			// get color and alpha from overlay icon
      			overlay->GetPixel(x + px, y + py, &r, &g, &b);
      			overlay->GetAlphaPixel(overlayAlpha, x + px, y + py, &a);
      
      			// get color and alpha from background icon if available
      			dest->GetPixel(px + xOffset, py + yOffset, &rr, &gg, &bb);
      			if (destAlpha)
      			{
      				dest->GetAlphaPixel(destAlpha, px + xOffset, py + yOffset, &aa);
      			}
      
      			// blend overlay color against existing icon background color
      			Float blend = a / 255.0;
      			dest->SetPixel(px + xOffset, py + yOffset, (Int32)Blend(rr, r, blend), (Int32)Blend(gg, g, blend), (Int32)Blend(bb, b, blend));
      
      			// use only the overlay alpha if the opacity is higher to keep the original icon complete
      			// and only in case the background has an alpha at all
      			if (destAlpha && aa < a)
      			{
      				dest->SetAlphaPixel(destAlpha, px + xOffset, py + yOffset, a);
      			}
      		}
      	}
      }
      

      Thanks for patience & support!

      Cheers,
      Frank

      posted in Cinema 4D SDK
      fwilleke80F
      fwilleke80
    • RE: Plugin compiled on macOS Catalina for R23, not working on Big Sur?

      Oh wait, got it by displaying the notarisation log. The binary had no timestamp.

      After googling a bit, I fixed it by adding --timestamp=http://timestamp.apple.com/ts01 to "Other Code Signing Flags", and it was notarised successfully! 🙂

      Is --timestamp=http://timestamp.apple.com/ts01 a good choice for this flag? The SDK docs just say "include a secure timestamp with your code-signing signature;", which is not too much information 😉

      posted in Cinema 4D SDK
      fwilleke80F
      fwilleke80
    • RE: Get Spline Data from document->GetActiveObject();

      Actually, I would use static_cast<SplineObject*>(op) instead of ToSpline(op) which is defined as only (SplineObject*)(op).
      After the appropriate tests (op->GetInfo() & OBJECT_ISSPLINE, op->IsInstanceOf(Ospline), et cetera), of course.

      posted in Cinema 4D SDK
      fwilleke80F
      fwilleke80
    • RE: Errors when recompiling R20 plugin to R21

      I don't know about the first one, but the second one is probably because you're not doing any error handling.

      Do something like this:

      myBaseArray.Append(plugin_id) iferr_ignore();
      

      or

      maxon::Result<void> SomeFunction()
      {
          myBaseArray.Append(plugin_id) iferr_return;
      }
      

      or

      void SomeFunction()
      {
          iferr_scope_handler
          {
              GePrint(err.GetMessage());
              return;
          };
          myBaseArray.Append(plugin_id) iferr_return;
      }
      

      or

      iferr (myBaseArray.Append(plugin_id))
      {
          GePrint(err.GetMessage());
      }
      

      Cheers,
      Frank

      posted in Cinema 4D SDK
      fwilleke80F
      fwilleke80
    • Using CodeEditor_Open()

      Hello,

      I'm experimenting with an object that accepts Python code. Therefore, I also need an "Open Editor" button to open the Python code in the code editor dialog.

      I have found this old thread here:
      https://developers.maxon.net/forum/topic/10352/13847_how-to-pass-python-code-to-c4ds-editor-window/2?_=1618561938314

      However, the code shown there does not work anymore.

      SCRIPTMODE enum

      bc.SetInt32(CODEEDITOR_SETMODE, SCRIPTMODE_PYTHON);
      

      I guess this must nowadays be:

      bc.SetInt32(CODEEDITOR_SETMODE, (Int32)SCRIPTMODE::PYTHON);
      

      GePythonGIL

      case CODEEDITOR_SETSTRING:
          {
            GePythonGIL gil;
            data->SetString(PARAMETER_PYTHONSCRIPT, msg.GetString(CODEEDITOR_SETSTRING));
        
            res = true;
            break;
          }
      

      First of all, there is no GePythonGIL anymore, so the code does not compile. Is maxon::py::CPythonGil the correct replacement?

      Second, what is it needed for? It's declared, but never used in this scope.

      PythonLibrary

      It seems this was in lib_py.h, but the SDK docs mention in the "Changes in R23" chapter that it's deprecated now. So, what should be used instead?

      pylib.CheckSyntax()

      I can also not find anything about CheckSyntax() in the current SDK docs.

      Callback function signature

      Even with an empty callback function that doesn't do anything, the code does not compile.

      No matching function for call to 'CodeEditor_Open' it says.

      The API defines the callback like this:

      Bool CodeEditor_Open(BaseList2D* obj, const maxon::Delegate<GeData(BaseList2D* obj, const BaseContainer& msg)>& callback, const BaseContainer& bc = BaseContainer());
      

      So it seems that has also changed since the thread I mentioned.

      . . .

      Long story short, I would really appreciate an up-to-date example of how to use CodeEditor_Open() and everything that has to do with it.

      Thanks in advance!

      Greetings,
      Frank

      posted in Cinema 4D SDK c++ r23 s24
      fwilleke80F
      fwilleke80
    • RE: Using CodeEditor_Open()

      Ah, ok. I didn't even know that the Python Generator supports a Message() function 😄

      Yes, that's exactly what I would like to have! The user should be able to implement some functions, and get provided with some variables to work with (like in the Python Effector).

      posted in Cinema 4D SDK
      fwilleke80F
      fwilleke80
    • RE: Using CodeEditor_Open()

      Your example, by the way, works fine! Thank you again for that! I'm currently trying to extend it to all my needs 🙂

      Therefore, here's another question:

      What if I don't want to have a BaseObject as a result, but something different, e.g. a BaseArray with values? The user would implement several different functions, some of which return a simple Int32 value, others get a BaseArray<Float> and should work on the array values, possibly changing them.

      maxon::BaseArray<Float> valueArray;
      
      auto* res = _scope.PrivateInvoke("MyFunctionThatFillsAnArrayOfFloats"_s, helperStack, maxon::GetDataType<maxon::specialtype::SomethingThatWorksWithAnArrayOfFloats*>(), &args.ToBlock()) iferr_return;
      
      if (res == nullptr)
      	return maxon::NullptrError(MAXON_SOURCE_LOCATION, "PrivateInvoke() result is NULL!"_s);
      
      maxon::specialtype::SomethingThatWorksWithAnArrayOfFloats* pyRes = res->Get<maxon::specialtype::SomethingThatWorksWithAnArrayOfFloats*>().GetValue();
      if (pyRes == nullptr)
      	return maxon::NullptrError(MAXON_SOURCE_LOCATION, "SomethingThatWorksWithAnArrayOfFloats result is NULL!"_s);
      

      Is there a maxon::specialtypethat allows me to pass a maxon::BaseArray<Float>, or a custom struct {} or even a maxon::BaseArray<MyStruct>?

      Thanks again! 🙂

      Cheers,
      Frank

      P.S.: The Python Field does something similar, it implements the Sample() function and passes all these neat custom data structures to it.

      posted in Cinema 4D SDK
      fwilleke80F
      fwilleke80
    • RE: R25 - Compile plugin issue

      Hi Ferdinand,

      I agree, but the problem is that projecttool also does play an automated inhouse-tool role, as you might still be aware of, and there these additional files are likely not welcome.

      I remember very well. I just didn't want to spoil any details here, without anyone from the SDK team mentioning it first 😉
      It does make sense in the Maxon context where one solution hosts a multitude of projects. Just for plugin developers, it usually results in having the Project Tool generate a nice project, but then having to build the solution manually, with all included framework projects, build dependencies (even between the framework projects), and references. That's error prone, generates more support noise for you, and annoys the plugin developers 😉

      But I will try to find a place for this idea, which then would have to be an option of the tool on a per project basis.

      I think there could be a very easy solution (well, at least it sounds easy... things are rarely as easy as they look on the outside, I know): The projetdefinition.txt specifies what type of file should be created:

      For projects:

      // Type of project - can be [Lib;DLL;App]
      Type=DLL
      

      For solutions:

      Type=Solution
      

      So, why not just allow something like this:

      Type=DLL,Solution
      

      It could simply do two complete runs then, one for each element in the enumerated Type. It would first do a normal run for Type=DLL, and then another one for Type=Solution. That would just require Project Tool to split the Type string by comma, and then iterate over the split parts and do one complete run for each of them.

      That way, plugin developers could have a much improved workflow when creating new projects, and maxon developers wouldn't be bothered with unwanted files.

      Currently, I still check in all my plugin project files for all Cinema 4D releases I support, into the source management, because regenerating them on the fly with Project Tool (which is, as far as I remember, the original idea behind it) is just way too much work.

      Cheers,
      Frank

      posted in Cinema 4D SDK
      fwilleke80F
      fwilleke80
    • RE: CustomGui and CustomDataType examples... not used anywhere?

      I've set it back to SOLVED again.

      Just a hint for anyone else who might run into a similar problem: Make sure your CustomDataTypeClass::Compare() impementation does not falsely return 0 under certain conditions.

      The SDK example goes the easy route: It even says in the comments: "this just compares the number of points ... a better implementation would also compare the values of the points". What would be a good way to compare arbitrary data like the maxon::BaseArray<Vector> from the SDK example, and assign the "1" or "-1" results?

      Sometimes, the data isn't less or greater, sometimes it's just different.

      Cheers,
      Frank

      posted in Cinema 4D SDK
      fwilleke80F
      fwilleke80