Navigation

    • Register
    • Login
    • Search
    • Categories
    1. Home
    2. ferdinand
    3. Posts
    • Profile
    • Following
    • Followers
    • Topics
    • Posts
    • Best
    • Groups

    Posts made by ferdinand

    • RE: GetDocumentPath() returning wrong path?

      Hi @Chris-Chris,

      ah, I understand now, sorry, should have understood that from the first posting, but somehow my brain blocked it :D. You got it a bit backwards if I do understand your first posting correctly. I am looking here especially at the last sentence of your first posting.

      What is my understanding is that the handler for MSG_MULTI_CLEARSUGGESTEDFOLDER should add the new path to my asset, not remove it because this was already done in MSG_GETALLASSETS?

      MSG_GETALLASSETS is meant to retrieve the full absolute paths of all assets of your plugin that need to be handled. Adding there a relative path will cause a reference error, because there is no relative file yet. After that Cinema will broadcast MSG_MULTI_CLEARSUGGESTEDFOLDER, so that you can turn the previously submitted absolute paths into relative ones, e.g., chopp everything of except the filename. There is an example in the documentation which shows the whole procedure.

      Cheers,
      Ferdinand

      posted in Cinema 4D Development
      ferdinand
    • RE: Create a Take

      Hello @pim,

      without any further feedback or questions, we will consider this topic as solved by Wednesday and flag it accordingly.

      Thank you for your understanding,
      Ferdinand

      posted in Cinema 4D Development
      ferdinand
    • RE: How to delete obj in the environment from a button in userdata

      Hello @JH23,

      without any further feedback or questions, we will consider this topic as solved by Wednesday and flag it accordingly.

      Thank you for your understanding,
      Ferdinand

      posted in Cinema 4D Development
      ferdinand
    • RE: Importing morph problem

      Hello @AiMiDi,

      without any further feedback or questions, we will consider this topic as solved by Wednesday and flag it accordingly.

      Thank you for your understanding,
      Ferdinand

      posted in Cinema 4D Development
      ferdinand
    • RE: GetDocumentPath() returning wrong path?

      Hello @Chris-Chris,

      thank you for reaching out to us. There are two problems with your approach I think, at least when I do understand you correctly. When you say:

      I like to know the path to the document when the user does a "Save With Assets" command.

      I would assume that with "the path to the document" you mean the path of the newly created document where all the assets have been localized, right? If that is the case, the line Filename fullDocPath = pDoc->GetDocumentPath() + pDoc->GetDocumentName(); is problematic. I also would assume pDoc is the document attached to your node you retrieved somewhere at an earlier point. Which is a bit risky, since there is no guratee that the NodeData::Message call is being made for the same document. But even if you would write it like this,

      BaseDocument* pDoc = node->GetDocument();
      Filename fullDocPath = pDoc->GetDocumentPath() + pDoc->GetDocumentName();
      

      there would still be the problem that you do retrieve the file path of the document that the node is attached to (which could also be empty), not the file path of the document that is going to be saved.

      Which brings us to the second problem. MSG_GETALLASSETS(Link) is being broadcasted in the context of collecting all information for saving a document with all assets. So, in the scope of that message the new file path is not yet defined. The message ID which seems more appropriate for your use case is MSG_DOCUMENTINFO_TYPE_SAVEPROJECT_AFTER, which is part of the DOCUMENTIFNO message family. This message is being broadcasted after the user has saved a document with all assets. The file path of the saved document is then embedded within the message data, which is of type DocumentInfoData(Link).

      I hope this helps and answers your question. Because I am a bit lost about the second part of your posting. So if anything remains unclear, I will have to ask you to reiterate.

      Cheers,
      Ferdinand

      posted in Cinema 4D Development
      ferdinand
    • RE: [FWD] How to retrieve the index of the closest polygon in a mesh to a point?

      Dear user,

      the problem with your example code is that you do use a VoxelizationInterface which is not well suited for this task. The type DistanceQueryInterface(Documentation) is much better suited for the task. You should also be more cautious with statements like the following, taken from your code,

      voxelRef = maxon::PolyVoxelization().Create() iferr_ignore("Could not create VoxelizationRef");
      

      as stepping over a failure of initialization will lead to crashes. At the end of this posting you will find an example for how to use DistanceQueryInterface as well as some light comments on error handling.

      I hope this helps and cheers,
      Ferdinand

      /* This function expects a point object (A) to be the first object in the scene, followed by a 
         polygon object (B) as the second object. It will then return the index for closest polygon
         in B for each vertex in A.
      */
      static maxon::Result<void> Pc13296(BaseDocument* doc)
      {
      	iferr_scope;
      
      	// Get the first and second node in the scene. The first one is expected
      	// to be a point node, serving as a point source, and the second one is
      	// expected to be a polygon node, serving as a polygon source.
      	BaseObject* node = doc->GetFirstObject();
      	if (node == nullptr || !node->IsInstanceOf(Opoint))
      	{
      		ApplicationOutput("Please provide a point object as the first node in the scene."_s);
      		return maxon::OK;
      	}
      	PointObject* pointNode = static_cast<PointObject*>(node);
      
      	node = node->GetNext();
      	if (node == nullptr || !node->IsInstanceOf(Opolygon))
      	{
      		ApplicationOutput("Please provide a polygon object as the second node in the scene."_s);
      		return maxon::OK;
      	}
      	PolygonObject* polygonNode = static_cast<PolygonObject*> (node);
      
      	// Get access to the point and polygon data and the global transforms of both nodes.
      	const Vector* points = pointNode->GetPointR();
      	const CPolygon* polygons = polygonNode->GetPolygonR();
      	const int pointCount = pointNode->GetPointCount();
      	const int polygonCount = polygonNode->GetPolygonCount();
      	Matrix mgPointNode = pointNode->GetMg();
      	// We invert it right away, as we only will need it in this form.
      	Matrix iMgPolygonNode = ~polygonNode->GetMg();
      
      	// When an error is being raised by Cinema we should be cautious with using iferr_ingore, as
      	// then the code will keep running in the current scope. In some cases like manipulating an 
      	// array like structure this can be useful. But when an initialization of an entity fails, we
      	// almost never want to keep going, as trying to use that not initialized object then will be
      	// a sure fire way to crash Cinema 4D. Instead we can either use iferr_return to simply leave 
      	// the current scope, use iferr_throw to throw a specific error or handle it manually with
      	// iferr(). Below you will find a two examples.
      
      	// Create a reference to the distance query interface.
      	maxon::DistanceQueryRef distanceQuery = maxon::DistanceCalculator().Create() iferr_return;
      
      	// Pass true for the second argument to voxelize the input to speed up larger queries. Just as
      	// with any optimization, for smaller data sets this voxelization setup might eat up all the
      	// performance benefits gained latter on.
      	iferr(distanceQuery.Init(polygonNode, false)) {
      		ApplicationOutput("Failed to initialize DistanceQueryInterfacae for @", polygonNode->GetName());
      		return maxon::OK;
      	}
      
      	// Go over all vertices and query them for the closest polygon (id) in the other mesh.
      	float distance = 0;
      	Vector p;
      	for (int poindId = 0; poindId < pointCount; poindId++)
      	{
      		// The point in the point mesh converted first to global coordinates and then to local
      		// coordinates in the polygon node. We have to do that because the points in the point
      		// source live in their own coordinate system as well the ones in the polygon source.
      		p = mgPointNode * iMgPolygonNode * points[poindId];
      		maxon::PrimitiveInformation info;
      		distance = distanceQuery.GetClosestMeshPrimitive(p, info);
      		ApplicationOutput("Closest polygon id for point id @: @", poindId, info.GetRealPolyIndex());
      	}
      
      	return maxon::OK;
      }
      

      The output/setup:
      642545d3-3ffa-467e-9123-cf0a328b18f1-image.png

      posted in Cinema 4D Development
      ferdinand
    • [FWD] How to retrieve the index of the closest polygon in a mesh to a point?

      Dear community,

      we received a support request via mail which we thought might also be interesting to the community. This thread captures the question asked in the mail.

      The question is "How to retrieve the index of the closest polygon in a mesh to a point?". The question has been asked for the C++ environment, looking for the right optimization data structure provided by the C++ SDK.

      Cheers,
      Ferdinand

      posted in Cinema 4D Development
      ferdinand
    • RE: Get the String of the Font Data?

      Hi @bentraje,

      thank you for reaching out to us and thank you @Cairyn for stepping in again.

      The normal behavior of FontData parameters is not to be initialized in Cinema 4D. For a newly allocated node, they then will return None in Python, indicating that they point to the default system font. In Python this default font has to be retrieved a bit awkwardly via c4d.bitmaps.GeClipMap, which has the static method GetDefaultFont(), returning a font container.

      A MoText node is the exception here, as it will then return an empty container. One must still default to the system default font then, but it is just an extra case to handle. Below is an example for that. I have also opened an issue for the inconsistency of MoText in this regard.

      Cheers,
      Ferdinand

      """Example for retrieving a node's PRIM_TEXT_FONT font container.
      
      For newly allocated nodes this will default to the default font internally,
      for nodes where the PRIM_TEXT_FONT parameter has been modified, by setting
      the font to bold for example, the font data will be there.
      
      MoText is somewhat a special case, as it does not align with the normal 
      behavior of FontData not being initialized.
      """
      
      import c4d
      
      def main():
          """
          """
          # Get the selected object.
          if not op:
              raise TypeError("Please select an object.")
      
          # Get the node's data container.
          nodeData = op.GetData()
          if nodeData is None:
              raise RuntimeError("")
      
          # Try to access the PRIM_TEXT_FONT parameter.
          fontData = nodeData[c4d.PRIM_TEXT_FONT]
          # When it is not populated, then fall back to the system font.
          if fontData is None or len(fontData.GetFont()) < 1:
              fontContainer = c4d.bitmaps.GeClipMap.GetDefaultFont(
                  c4d.GE_FONT_DEFAULT_SYSTEM)
          # Otherwise get the font container.
          else:
              fontContainer = fontData.GetFont()
      
          # Go over the font container.
          for cid, value in fontContainer:
              print (f"cid: {cid}, value: {value}")
      
      if __name__=='__main__':
          main()
      
      posted in Cinema 4D Development
      ferdinand
    • RE: Write a python code into a Python Effector

      Hi @Hugo-BATTISTELLA and @Cairyn,

      thank you for reaching out to us. And thank you @Cairyn for stepping in. We cannot add anything to what @Cairyn already said. We would like however to bring up the fact that topics should be single subject as defined in the Forum Guidelines, as otherwise content will be hard to find for other users. It is fine in this case, but in the future, please open multiple topics for questions that are not direct follow-up questions, but questions with a new scope.

      Thank you for your understanding,
      Ferdinand

      posted in Cinema 4D Development
      ferdinand
    • RE: LINK EFFECTORS TO CLONERS

      Hi @Hugo-BATTISTELLA,

      thank you for reaching out to us and thank you @bentraje for providing help.

      The major problem with your code is that you have the line Cloner.InsertObject(EffectorList) in there. But there is no variable of the name Cloner within the scope of effectorsCreation. Which is why your code will raise a NameError on this line. But even if the variable Cloner from your function cloner_creation would be visible within effectorsCreation, it would still raise an AttributeError, because your Cloner is of type BaseObject which has no attribute (i.e., method) of the name InsertObject.

      I would also recommend not to insatiate an c4d.InExcludeData from scratch, because you will lose any existing links by doing so and then writing back that newly allocated InExcludeData. In this case here it does not really matter, since you also instantiate the MoGraph cloner, i.e., its effectors list will still be empty. Below you can find a commented section of your code and a proposal how I would write something like that (which is effectively also what @bentraje did).

      Cheers,
      Ferdinand

      The code:

      import c4d
      
      # This is a lightly commented version of your code, pointing out the major
      # problem with your version.
      """
      def effectorsCreation():
          Effector = c4d.BaseList2D(1025800)
          # This does not do anything; it just creates an InexCludeData instance.
          EffectorList = c4d.InExcludeData() # Initialize an InExclude data
      	
          # That does not work, Cloner is not visible in this scope, it would also
          # be an BaseObject, which does not have a InsertObject method.
          Cloner.InsertObject(EffectorList) # Add effector to effectors list
          doc.InsertObject(Effector)
      """
      
      def CreateClonerSetup():
          """
          """
          # Instantiate a MoGraph cloner and Python effector. One can also use
          # BaseList2D.__init__(), this version here is just more verbose.
          cloner = c4d.BaseObject(1018544)
          effector = c4d.BaseObject(1025800)
          
          # Get a copy of the InExcludeData Effectors parameter of the cloner.
          inExcludeData = cloner[c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST]
          # Add the effector in an activated state (1)
          inExcludeData.InsertObject(effector, 1)
          # Important, we have to write the modified InExcludeData back, since we
          # only retrieved a copy of it.
          cloner[c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST] = inExcludeData
          
          # Add both nodes to the currently active document.
          doc.InsertObject(effector)
          doc.InsertObject(cloner)
          
          # Update Cinema 4D.
          c4d.EventAdd()
      
      if __name__=='__main__':
          CreateClonerSetup()
      
      posted in Cinema 4D Development
      ferdinand
    • RE: Node ID relevant bits?

      Hi @Virtualritz,

      without further questions or updates we will consider this topic as solved by Thursday and flag it accordingly.

      Cheers,
      Ferdinand

      posted in Cinema 4D Development
      ferdinand
    • RE: why the line break doesn't happen when I use self.GetString() in self.AddMultiLineEditText()

      Hi @pyxelrigger,

      without further questions or updates we will consider this topic as solved by Thursday and flag it accordingly.

      Cheers,
      Ferdinand

      posted in Cinema 4D Development
      ferdinand
    • RE: ObjectData::Draw

      Hi @AiMiDi,

      thank you for reaching out to us. We are not 100% certain what you mean with:

      Now it looks like there is a problem with the drawing order?

      , but I would assume you want the handle to be drawn in front of the cube-like geometry and not behind it? This is caused by drawing into the draw-pass DRAWPASS::OBJECT. Changing the drawing space to a screen projection while in the Object draw pass will not bring the drawn content in front of everything else, as this just means using another coordinate system and nothing else (as done in your code with const Vector& screenSpacePos = bd->WS(op->GetMg().off);).

      You probably want to draw into the draw-pass HANDLES, as this is the draw pass into which one is meant to draw handles. Relevant in this context might also be the DRAWPASS enum and this older blog post of ours.

      I hope this helps, if anything remains unclear, please do not hesitate to ask.

      Cheers,
      Ferdinand

      posted in Cinema 4D Development
      ferdinand
    • RE: Limit SplineKnot tangent

      Hi @AiMiDi,

      thank you for reaching out to us.

      In principle your question of clamping a parameter is answered with overwriting NodeData::Message. Where one then listens for MSG_DESCRIPTION_POSTSETPARAMETER to modify the just set value according to ones needs and then writes it back. This works fine for atomic data types like DTYPE_REAL, but there is a problem when approaching it like that with more complex data types with more complex GUIs, e.g, SplineData.

      The problem is that one cannot access an instance of a parameter, but only a copy of it with NodeData::GetParameter, which in turn forces one to write back the modified data. For SplineData this means that this will flush the current vertex selection state of the gadget, making this approach unviable, as it will render the user incapable of selecting a vertex. This also applies to the case of MSG_DESCRIPTION_USERINTERACTION_START and MSG_DESCRIPTION_USERINTERACTION_END, resulting in the same problem of making a vertex selection in the SplineData will immediately flush it.

      An alternative route is to use SplineData::SetUserCallback to establish a callback with which to intercept changes made to the SplineData via SPLINE_CALLBACK_CORE_MESSAGE. The problem here is that this will work like MSG_DESCRIPTION_USERINTERACTION_END, i.e., it is only going to be raised once the user does release the mouse - ends the interaction. And not while dragging a value like MSG_DESCRIPTION_POSTSETPARAMETER is being raised for. I currently do not see a more elegant way to do this. You will find an example for that at the end of the posting.

      There is also the problem of a slight ambiguity in your question, since:

      I want to limit the maximum and minimum values of the tangent of SplineKnot so that it does not exceed the maximum and minimum values of SplineKnot.

      could be interpreted in multiple ways. I went in my solution for the simplest interpretation of projecting the spline handles orthogonally back onto the [0, 1] interval box in the GUI, if you want this to be fancier, you will have to do the line-line intersection calculation (between a border of the box and your tangent vector) yourself.

      I hope this helps and cheers,
      Ferdinand

      The code (for an ObjectData example):

      // For https://plugincafe.maxon.net/topic/13277
      
      #include "c4d.h"
      #include "c4d_symbols.h"
      #include "obase.h"
      #include "maxon/apibase.h"
      #include "c4d_objectdata.h"
      #include "c4d_basedocument.h"
      #include "lib_description.h"
      #include "customgui_splinecontrol.h"
      
      // The callback function which does clamp the SplineData
      static bool SplineDataCallBack(Int32 cid, const void* data)
      {
      	// There are other callback types we are just using SPLINE_CALLBACK_CORE_MESSAGE here.
      	if (cid == SPLINE_CALLBACK_CORE_MESSAGE)
      	{
      		if (data == nullptr)
      			return true;
      
      		// Some data massaging ...
      		SplineDataCallbackCoreMessage* coreMessageData = (SplineDataCallbackCoreMessage*) data;
      		SplineCustomGui* splineCustomGui = coreMessageData->pGUI;
      		SplineData* splineData = splineCustomGui->GetSplineData();
      		if (splineData == nullptr)
      			return true;
      		
      		// Now we just go over all knots and clamp them. This could also be done more elegantly
      		// with line-line intersections, I am just clamping each tangent here to the interval [0, 1],
      		// which effectively equates orthogonally projecting the tangents onto that "[0,  1] box". If you
      		// want to scale vectors so that a handle which is outside that [0, 1] box, is scaled in such
      		// manner that the handle touches the border of that box, you will have to use line-line 
      		// intersections.
      		for (int i = 0; i < splineData->GetKnotCount(); i++)
      		{
      			// Get the current knot.
      			CustomSplineKnot* knot = splineData->GetKnot(i);
      			// Tangents live in a vector space relative to their vertex, so we make the tangent vectors
      			// global first and then clamp them.
      			Vector globalLeftTangent = (knot->vPos + knot->vTangentLeft).Clamp01();
      			Vector globalRightTangent = (knot->vPos + knot->vTangentRight).Clamp01();
      			// And then we convert the tangents back to their local tangent space and write them back.
      			knot->vTangentLeft = globalLeftTangent - knot->vPos;
      			knot->vTangentRight = globalRightTangent - knot->vPos;
      		}
       	}
      	return true;
      }
      
      class Pc13277 : public ObjectData
      {
      	INSTANCEOF(Pc13277, ObjectData)
      
      public:
      	static NodeData* Alloc() { return NewObjClear(Pc13277); }
      
      	// We bind the callback on the initialization of the node.
      	virtual Bool Init(GeListNode* node)
      	{
      		if (node == nullptr || !SUPER::Init(node))
      			return false;
      		BaseContainer* bc = ((BaseList2D*)node)->GetDataInstance();
      
      		// Create a SplineData instance and set the callback function.
      		GeData geData(CUSTOMDATATYPE_SPLINE, DEFAULTVALUE);
      		SplineData* splineData = (SplineData*)geData.GetCustomDataType(CUSTOMDATATYPE_SPLINE);
      		splineData->SetUserCallback(SplineDataCallBack, nullptr);
      
      		// Populate the SplineData as usual ...
      		if (splineData)
      			splineData->MakeLinearSplineBezier(2);
      		bc->SetData(ID_SPLINEDATA, geData);
      
      		return true;
      	}
      
      	// And when something replaces our spline data instance.
      	virtual bool SetDParameter(GeListNode* node, const DescID& id, const GeData& t_data, DESCFLAGS_SET& flags) 
      	{
      		if (id[0].id == ID_SPLINEDATA) 
      		{
      			SplineData* splineData = (SplineData*)t_data.GetCustomDataType(CUSTOMDATATYPE_SPLINE);
      			splineData->SetUserCallback(SplineDataCallBack, nullptr);
      		}
      		return SUPER::SetDParameter(node, id, t_data, flags);
      	}
      };
      

      How it will work:
      456.gif

      posted in Cinema 4D Development
      ferdinand
    • RE: Python Generator Mimicking Cloner That Can Modify Individual Cloner Parameters (Except PSR)?

      Hi @bentraje,

      thank you for reaching out to us. As @Cairyn already said, there is little which prevents you from writing your own cloner. Depending on the complexity of the cloner, this can however be quite complex. Just interpolating between two positions or placing objects in a grid array is not too hard. But recreating all the complexity of a MoGraph cloner will be. It is also not necessary. Here are two more straight forward solutions:

      1. As I said in the last thread (the one you did link), MoGraph is perfectly capable of driving arbitrary parameters of an object. I overlooked in the old thread that you did ask what I meant with ""drive the attributes of an object or its material"", so the file cube_bend_mograph.c4d shows you how you can drive the strength parameter of a bend object with MoGraph. The difference I then made was that while you cannot drive such attributes directly via the particle (arrays), but you can let MoGraphs interpolation system take care of it. Please keep in mind that I am everything but an expert for MoGraph. There are probably more elegant solutions for all this, but all this is out of scope for this forum. Please contact technical support or refer to Cineversity for questions about MoGraph.
      2. One problem of MoGraph is that driving more than one arbitrary parameter of an object is a bit tedious. Since MoGraph's sole principle is interpolation and it can only interpolate on a single axis (between parameters), you will then have to start interpolating between two cloners to drive a second, third, fourth, etc. parameter. Here it would make sense to write a custom solution to have less complicated setups. Rather than rewriting a cloner, it would be however more sensible to just modify the cache of a cloner. This will only work when the cloner is in the "Instance Mode" "Instance", as otherwise the cache will only contain the instance wrappers (a bit confusing the naming here ^^). You could do this all inside a GVO, including building the cloner and its cache, but I did provide here a solution which relies on linking a cloner whose cache one wishes to modify. The idea is then simple, walk over all bend objects in the cache and modify their bend strength. You can find a scene file and the code below.

      Cheers,
      Ferdinand

      file: cube_bend_python.c4d
      code:

      """Example for modifying the cache of a node and returning it as the output
      of a generator.
      
      This specific example drives the bend strength of bend objects contained in
      a Mograph cloner object. The example is designed for a Python generator 
      object with a specific set of user data values. Please use the provided c4d
      file if possible.
      
      Note:
          This example makes use of the function `CacheIterator()` for cache 
          iteration which has been proposed on other threads for the task of walking
          a cache, looking for specific nodes. One can pass in one or multiple type
          symbols for the node types to be retrieved from the cache. I did not 
          unpack the topic of caches here any further.
      
          We are aware that robust cache walking can be a complex subject and 
          already did discuss adding such functionality to the SDK toolset in the
          future, but for now users have to do that on their own.
      
      As discussed in:
          plugincafe.maxon.net/topic/13275/
      """
      
      import c4d
      
      # The cookie cutter cache iterator template, can be treated as a black-box,
      # as it has little to do with the threads subject.
      def CacheIterator(op, types=None):
          """An iterator for the elements of a BaseObject cache.
      
          Handles both "normal" and deformed caches and has the capability to 
          filter by node type.
      
          Args:
              op (c4d.BaseObject): The node to walk the cache for.
              types (Union[list, tuple, int, None], optional): A collection of type
               IDs from one of which a yielded node has to be derived from. Will
               yield all node types if None. Defaults to None.
      
          Yields:
              c4d.BaseObject: A cache element of op.
      
          Raises:
              TypeError: On argument type violations.
          """
          if not isinstance(op, c4d.BaseObject):
              msg = "Expected a BaseObject or derived class, got: {0}"
              raise TypeError(msg.format(op.__class__.__name__))
      
          if isinstance(types, int):
              types = (types, )
          if not isinstance(types, (tuple, list, type(None))):
              msg = "Expected a tuple, list or None, got: {0}"
              raise TypeError(msg.format(types.__class__.__name__))
      
          # Try to retrieve the deformed cache of op.
          temp = op.GetDeformCache()
          if temp is not None:
              for obj in CacheIterator(temp, types):
                  yield obj
      
          # Try to retrieve the cache of op.
          temp = op.GetCache()
          if temp is not None:
              for obj in CacheIterator(temp, types):
                  yield obj
      
          # If op is not a control object.
          if not op.GetBit(c4d.BIT_CONTROLOBJECT):
              # Yield op if it is derived from one of the passed type symbols.
              if types is None or any([op.IsInstanceOf(t) for t in types]):
                  yield op
      
          # Walk the hierarchy of the cache.
          temp = op.GetDown()
          while temp:
              for obj in CacheIterator(temp, types):
                  yield obj
              temp = temp.GetNext()
      
      
      def main():
          """
          """
          # The user data.
          node = op[c4d.ID_USERDATA, 1]
          angle = op[c4d.ID_USERDATA, 2]
          fieldList = op[c4d.ID_USERDATA, 3]
      
          # Lazy parameter validation ;)
          if None in (node, angle, fieldList):
              raise AttributeError("Non-existent or non-populated user data.")
      
          # Get the cache of the node and clone it (so that we have ownership).
          cache = node.GetDeformCache() or node.GetCache()
          if cache is None:
              return c4d.BaseObject(c4d.Onull)
          clone = cache.GetClone(c4d.COPYFLAGS_NONE)
      
          # Iterate over all bend objects in the cache ...
          for bend in CacheIterator(clone, c4d.Obend):
              # ..., sample the field list for the bend object position, ...
              fieldInput = c4d.modules.mograph.FieldInput([bend.GetMg().off], 1)
              fieldOutput = fieldList.SampleListSimple(op, fieldInput,
                                                       c4d.FIELDSAMPLE_FLAG_VALUE)
              if (not isinstance(fieldOutput, c4d.modules.mograph.FieldOutput) or
                      fieldOutput.GetCount() < 1):
                  raise RuntimeError("Error sampling field input.")
              # ... and set the bend strength with that field weight as a multiple 
              # of the angle defined in the user data.
              bend[c4d.DEFORMOBJECT_STRENGTH] = angle * fieldOutput.GetValue(0)
      
          # Return the clone's cache.
          return clone
      
      posted in Cinema 4D Development
      ferdinand
    • RE: Content libraries. How to change path?

      Hi @leex,

      unfortunately, Maxon does not run a technical support forum. We deliver technical support via a ticket system as pointed to in my last posting, because we believe we can deliver higher quality support in this manner. When you are on the lookout for a Cinema 4D community, I would recommend either core4d.com or the Cinema 4D forum on CGTalk. There is also a plethora of non-English speaking forums, especially for the European language domain.

      Cheers,
      Ferdinand

      posted in Cinema 4D Development
      ferdinand
    • RE: Content libraries. How to change path?

      Hello @leex,

      welcome to the Plugin Café. This is a forum geared towards users who want to develop plugins for Cinema 4D. We do not deliver technical support for Cinema 4D here. You can reach technical support via our ticket system. Also - we held our yearly company summit this week, which is why almost everyone has gotten an extra holiday today. So, you most likely will not reach anyone on technical support until Monday.

      Thank you for your understanding,
      Ferdinand

      posted in Cinema 4D Development
      ferdinand
    • RE: GetContour() and return Onull

      Hi @d_schmidt,

      thank for your video, that helped quite a lot. Your conclusion was right, there are multiple documents at play here. In the case of your DoubleCircleData node representation being linked in a Cloner and having a empty spline next to it, the Cloner bugs out and something like visualized by console output below happens:

      GetContour() called with document at 00000231f8fd4240
      CheckDirty() and dirty requirements met with doc: 00000231a99c1e40 and nextNode: 0000000000000000.
      GetContour() called with document at 00000231a99c1e40
      CheckDirty() and dirty requirements met with doc: 00000231a99c1e40 and nextNode: 0000000000000000.
      GetContour() called with document at 00000231a99c1e40
      

      There are at least two documents at play here and the amount of calls, here three in total, are more than we would expect here. I also had cases where I had five calls in a row or just two. Also the next node in that copied document is not present, i.e., the call op->GetNext() in your GetContour will return a nullptr then; we can see this in line 2 where it says in CheckDirty nextNode: 0000000000000000.

      When we instead either do not link your plugin in a cloner or put something else than an empty spline object next to it, the behaviour will then be correct and take a form similar to:

      GetContour() called with document at 00000231f8fd4240 and _documentTransferCache at 0000000000000000`
      

      You might be wondering at this point what that _documentTransferCache is. This is part of my little work around I did provide, which basically relies on Noddedata::CopyTo and manually curating this very special case. You can read a bit more about it in the file provided below. At least for me this works now like I would expect it to work. I also did provide a demo file. As stated in the code below, I would however not really recommend doing this, unless you REALLY REALLY need this. My solution is rather hacky, but I do not see another way of fixing the mess the cloner causes. I would either inform my users about that known problem or look for an alternative way to design my plugin. I will report this behaviour of the Cloner as a bug, but it might take some time until someone will fix it.

      Cheers,
      Ferdinand

      The test-scene:
      naughty-cloner.c4d
      The code:

      /*
      A rather hacky way to deal with your Cloner & empty spline problem.
      
      I actually would not suggest using this, because this solution is so unnecessarily complicated for a
      special corner case that is does not seem to be worth it. This is definitely caused by an, *cough*,
      *oddness* of the Cloner object in that special empty spline object case.
      	
      I would rather suggest to either:
          - Reject empty spline objects as links in your BaseLink.
          - Simply tell your users that this is a known problem and warn them.
      
      While this solution here works, there are so many ifs and buts at play, that it seems rather unlikely
      that this will work in every conceivable scenario. I will file a bug report for the Cloner about this,
      but due to its niche-case nature I would not hold my breath for a short term fix.
      
      As discussed in:
      	https://plugincafe.maxon.net/topic/13231/
      */
      
      
      #include "c4d.h"
      #include "c4d_symbols.h"
      #include "odoublecircle.h"
      #include "main.h"
      
      #define ID_CIRCLEOBJECT 1001154
      
      class DoubleCircleData : public ObjectData
      {
      	INSTANCEOF(DoubleCircleData, ObjectData)
      
      public:
      	virtual Bool Init(GeListNode* node);
      	virtual SplineObject* GetContour(BaseObject* op, BaseDocument* doc, Float lod, BaseThread* bt);
      	virtual void CheckDirty(BaseObject* op, BaseDocument* doc);
      	virtual Bool CopyTo(NodeData* dest, GeListNode* snode, GeListNode* dnode, COPYFLAGS flags, AliasTrans* trn);
      
      	static NodeData* Alloc() { return NewObjClear(DoubleCircleData); }
      
      private:
      	BaseObject* _nextNodeTransferCache = nullptr;
      	BaseDocument* _docmentTransferCache = nullptr;
      	BaseObject* _nextNodeCache = nullptr;
      	Int32 _prevFrameCache = 0;
      	Bool _hasInitalised = false;
      };
      
      Bool DoubleCircleData::Init(GeListNode* node)
      {
      	BaseObject* op = (BaseObject*)node;
      	BaseContainer* data = op->GetDataInstance();
      	if (!data)
      		return false;
      
      	data->SetFloat(CIRCLEOBJECT_RAD, 200.0);
      	data->SetInt32(PRIM_PLANE, 0);
      	data->SetBool(PRIM_REVERSE, false);
      	data->SetInt32(SPLINEOBJECT_INTERPOLATION, SPLINEOBJECT_INTERPOLATION_ADAPTIVE);
      	data->SetInt32(SPLINEOBJECT_SUB, 8);
      	data->SetFloat(SPLINEOBJECT_ANGLE, DegToRad(5.0));
      	data->SetFloat(SPLINEOBJECT_MAXIMUMLENGTH, 5.0);
      	return true;
      }
      // We are implementing CopyTo to copy over data for that buggy Cloner case.
      Bool DoubleCircleData::CopyTo(NodeData* dest, GeListNode* snode, GeListNode* dnode, COPYFLAGS flags, AliasTrans* trn) 
      {
      	// In case this node is getting copied, we copy over the old document and the next node.
      	DoubleCircleData* impl = static_cast<DoubleCircleData*>(dest);
      
      	// This the "source" case, i.e., when we are actually in the document we *want* to be in.
      	if (_nextNodeTransferCache == nullptr) 
      	{
      		impl->_nextNodeTransferCache = _nextNodeCache;
      		impl->_docmentTransferCache = snode->GetDocument();
      	}
      	// Here we are copying a copy, so this will run when our node has been cloned and is then getting cloned again.
      	else
      	{
      		impl->_nextNodeTransferCache = _nextNodeTransferCache;
      		impl->_docmentTransferCache = _docmentTransferCache;
      	}
      	return NodeData::CopyTo(dest, snode, dnode, flags, trn);
      }
      
      void DoubleCircleData::CheckDirty(BaseObject* op, BaseDocument* doc)
      {
      	// The same as yours, it only can also handle documents with a negative document time, specifically frame -1.
      	BaseObject* nextNode = op->GetNext();
      	Int32 currentFrame = doc->GetTime().GetFrame(doc->GetFps());
      
      	if (!_hasInitalised || nextNode != _nextNodeCache || _prevFrameCache != currentFrame)
      	{
      		_prevFrameCache = currentFrame;
      		_nextNodeCache = nextNode;
      		_hasInitalised = true;
      
      		op->SetDirty(DIRTYFLAGS::DATA);
      		ApplicationOutput("CheckDirty() and dirty requirements met with doc: @ and nextNode: @.", (void*)doc, (void*)nextNode);
      	}
      }
      
      SplineObject* DoubleCircleData::GetContour(BaseObject* op, BaseDocument* doc, Float lod, BaseThread* bt)
      {
      	BaseContainer* bc = op->GetDataInstance();
      	if (!bc)
      		return nullptr;
      
      	// Some console chirping to see what is going on here. The cloner copies stuff all over the place in case we have an
      	// empty spline next to our DoubleCircleData GeListNode representation.
      	ApplicationOutput("GetContour() called with document at @ and _documentTransferCache at @", (void*)doc, (void*)_docmentTransferCache);
      	ApplicationOutput("_nextNodeCache: @, _nextNodeTransferCache: @.", (void*)_nextNodeCache, (void*)_nextNodeTransferCache);
      
      	// op->GetNext() will "fail" in the case our DoubleCircleData GeListNode representation being linked in a Cloner as a cloning
      	// "surface". next will then be a nullptr, although it should point to the empty spline. This has nothing to do with node order or
      	// other things one might suspect, but only with the fact if the spline has vertices or not. For a spline with vertices evrything will
      	// behave normally.
      	// I have not dug around in the depth of the Cloner implementation to see what is happening in detail there, but this would not
              // help us much here for a short term solution anyways.
      
      	// BaseObject* next = op->GetNext();
      
      	// Instead we fall back onto our _nextNodeTransferCache when this document has a _docmentTransferCache which is not null.
      	BaseObject* next = nullptr;
      	// We have a document transfer cache, so we fall back onto our "actual" node.
      	if (_docmentTransferCache != nullptr)
      	{
      		next = _nextNodeTransferCache;
      	}
      	// We are in the original document.
      	else
      	{
      		next = op->GetNext();
      	}
      
      	if (next == nullptr || next->GetType() != Ospline)
      	{
      		SplineObject* rs = SplineObject::Alloc(4, SPLINETYPE::LINEAR);
      		if (!rs)
      		{
      			return nullptr;
      		}
      		Vector* padr = rs->GetPointW();
      		padr[0] = Vector(100, 0, 0);
      		padr[1] = Vector(0, 100, 0);
      		padr[2] = Vector(-100, 0, 0);
      		padr[3] = Vector(0, -100, 0);
      		rs->Message(MSG_UPDATE);
      
      		//ApplicationOutput("NextNoSplineCondition, returned dummy geom."_s);
      
      		return rs;
      	}
      	// else
      
      	SplineObject* inputspline = static_cast<SplineObject*>(next);
      	Int32 pointcount = inputspline->GetPointCount();
      	const Vector* points = inputspline->GetPointR();
      
      	SplineObject* rs = SplineObject::Alloc(pointcount, SPLINETYPE::LINEAR);
      	if (!rs)
      	{
      		return nullptr;
      	}
      	Vector* padr = rs->GetPointW();
      	for (Int32 x = 0; x < pointcount; x++)
      	{
      		padr[x] = points[x];
      	}
      	rs->Message(MSG_UPDATE);
      	//ApplicationOutput("IsSplineCondition, returned copied spline."_s);
      	return rs;
      
      }
      
      Bool RegisterCircle()
      {
      	return RegisterObjectPlugin(ID_CIRCLEOBJECT, GeLoadString(IDS_CIRCLE), OBJECT_GENERATOR | OBJECT_ISSPLINE, DoubleCircleData::Alloc, "Odoublecircle"_s, AutoBitmap("circle.tif"_s), 0);
      }
      
      posted in Cinema 4D Development
      ferdinand
    • RE: startup script in R23 not working anymore

      Hi @marc_angele,

      thank you for reaching out to us. Your problem is caused due to a change of how Python is integrated with R23+ compared to R20.

      • First of all, you are not really supposed to modify {USERPREFS}/python37/maxon_generated/__init__.py, as this is a file for internal usage. So this was never a feature in the first place ;)
      • You can however still modify it, and it will still work as long as you have a plugin installed which imports the maxon package. Your code will be then executed when this plugin imports the package.
      • Better solutions would be however:
        a. Create a file called python_init.py as{USERPREFS}/python37/python_init.py. It will mostly work like an __init__.py, here you can find its (slightly outdated) documentation.
        b. Implement some kind of plugin and implement PluginMessage() and hook into C4DPL_INIT_SYS or C4DPL_INIT which are very early points to execute code in the boot up of Cinema 4D. Both for python_init.py and PluginMessage() applies that due to this early stage of the boot-up of Cinema, you won't have full functionality of all modules yet.

      For more complex tasks you might have to run a script with c4dpy before you actually start Cinema or postpone your code to a later point in PluginMessage.

      Cheers,
      Ferdinand

      posted in Cinema 4D Development
      ferdinand
    • RE: GetContour() and return Onull

      Hi @d_schmidt,

      sorry, I did overlook that you only posted a fragment of your CheckDirty, and that your full code is:

      void DoubleCircleData::CheckDirty(BaseObject* op, BaseDocument* doc)
      {
          BaseObject* next = op->GetNext();
          Int32 currentframe = doc->GetTime().GetFrame(doc->GetFps());
          if (_prevFrame != currentframe)
          {
              _prevFrame = currentframe;
             
              op->SetDirty(DIRTYFLAGS::DATA);
          }
          if (next != _nextCache)
          {
              _nextCache = next;
              op->SetDirty(DIRTYFLAGS::DATA);
          }
      }
      

      I cannot reproduce the faulty behavior you mentioned with your code, see gif below which runs your compiled code, when does that happen for you?

      1.gif

      edit: One thing that just came to mind when looking at your screen above:

      alt text

      It looks like your spline did update, because I cannot see the spline representation anymore, the only thing that did not seem to have updated is the cloner which clones the spheres onto your spline, it still seems to work on an older cache of your object. Can you confirm that?

      Cheers,
      Ferdinand

      posted in Cinema 4D Development
      ferdinand