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. HerrMay
    3. Posts
    H
    • Profile
    • Following 0
    • Followers 0
    • Topics 20
    • Posts 62
    • Best 7
    • Controversial 0
    • Groups 0

    Posts made by HerrMay

    • RE: Issue collecting all the shaders in a material

      Hi @John_Do,

      great to hear that I could be of help and that it works for you. 🙂

      Yes, I find it myself often enough hard to understand recursive code as well. I guess it's true what they say.
      "To understand recursion one must first understand recursion." 😄

      You guessed correctly. The variable terminator is doing nothing in this case. As does the equality check against node. It is (for each loop) assigned to the incoming node which is the current material that is processed in each loop cycle. So node and terminator are essentially the same in this context. 😉

      Cheers,
      Sebastian

      posted in Cinema 4D SDK
      H
      HerrMay
    • RE: Issue collecting all the shaders in a material

      Hi @John_Do,

      the multiple loop of some shaders can indeed come from the way the TraverseShaders function iterates the shader tree. Additionally theat function has another drawback as its iterating the tree utilizing recursion which can lead to problems on heavy scenes.

      Find below a script that uses an iterative approach of walking a shader tree. As I don't have access to Corona I could only test it for standard c4d materials.

      Cheers,
      Sebastian

      import c4d
      
      doc: c4d.documents.BaseDocument  # The active document
      
      def iter_shaders(node):
          """Credit belongs to @ferdinand from the Plugincafe. I added only the part with the material and First Shader checking.
          
          Yields all descendants of ``node`` in a truly iterative fashion.
      
          The passed node itself is yielded as the first node and the node graph is
          being traversed in depth first fashion.
      
          This will not fail even on the most complex scenes due to truly
          hierarchical iteration. The lookup table to do this, is here solved with
          a dictionary which yields favorable look-up times in especially larger
          scenes but results in a more convoluted code. The look-up could
          also be solved with a list and then searching in the form ``if node in
          lookupTable`` in it, resulting in cleaner code but worse runtime metrics
          due to the difference in lookup times between list and dict collections.
          """
          if not node:
              return
      
          # The lookup dictionary and a terminal node which is required due to the
          # fact that this is truly iterative, and we otherwise would leak into the
          # ancestors and siblings of the input node. The terminal node could be
          # set to a different node, for example ``node.GetUp()`` to also include
          # siblings of the passed in node.
          visisted = {}
          terminator = node
      
          while node:
      
              if isinstance(node, c4d.Material) and not node.GetFirstShader():
                 break
              
              if isinstance(node, c4d.Material) and node.GetFirstShader():
                  node = node.GetFirstShader()
              
              # C4DAtom is not natively hashable, i.e., cannot be stored as a key
              # in a dict, so we have to hash them by their unique id.
              node_uuid = node.FindUniqueID(c4d.MAXON_CREATOR_ID)
              if not node_uuid:
                  raise RuntimeError("Could not retrieve UUID for {}.".format(node))
      
              # Yield the node when it has not been encountered before.
              if not visisted.get(bytes(node_uuid)):
                  yield node
                  visisted[bytes(node_uuid)] = True
      
              # Attempt to get the first child of the node and hash it.
              child = node.GetDown()
      
              if child:
                  child_uuid = child.FindUniqueID(c4d.MAXON_CREATOR_ID)
                  if not child_uuid:
                      raise RuntimeError("Could not retrieve UUID for {}.".format(child))
      
              # Walk the graph in a depth first fashion.
              if child and not visisted.get(bytes(child_uuid)):
                  node = child
      
              elif node == terminator:
                  break
      
              elif node.GetNext():
                  node = node.GetNext()
      
              else:
                  node = node.GetUp()
              
      def main():
          
          materials = doc.GetActiveMaterials()
          tab = " \t"
      
          for material in materials:
          
              # Step over non Corona Physical materials or Corona Legacy materials.
              if material.GetType() not in (1056306, 1032100):
                  continue
              
              print (f"{material = }")
      
              for shader in iter_shaders(material):
                  print (f"{tab}{shader = }")
                  
              print(end="\n")
      
      if __name__ == '__main__':
          main()
      
      posted in Cinema 4D SDK
      H
      HerrMay
    • RE: Issue collecting all the shaders in a material

      Hi @John_Do,

      I could imagine that you stumble upon the same problem I did back then. Have a look at this post.

      @ferdinand helped me back then. The key gotcha was that I didn't account for the branching nature of a shader tree. I have pasted the code to succesfully traverse such shader tree down below.

      Cheers,
      Sebastian

      import c4d
      
      doc: c4d.documents.BaseDocument  # The active document
      
      def TraverseShaders(node: c4d.BaseList2D) -> c4d.BaseShader:
          """Yields all shaders that are hierarchical or shader branch descendants of #node.
      
          Semi-iterative traversal is okay in this case, at least I did not bother with implementing it fully
          iteratively here.
      
          In case it is unclear, you can throw any kind of node at this, BaseObject, BaseMaterial, 
          BaseShader, etc., to discover shaders which a descendants of them.
          """
          if node is None:
              return
      
          while node:
              if isinstance(node, c4d.BaseShader):
                  yield node
      
              # The shader branch traversal.
              for descendant in TraverseShaders(node.GetFirstShader()):
                  yield descendant 
      
              # The hierarchical traversal.
              for descendant in TraverseShaders(node.GetDown()):
                  yield descendant
      
              node = node.GetNext()
              
      def main() -> None:
          """
          """
          material: c4d.BaseMaterial = doc.GetActiveMaterial()
          print (f"{material = }\n")
          for shader in TraverseShaders(material):
              print (shader)
      
      if __name__ == '__main__':
          main()
      
      posted in Cinema 4D SDK
      H
      HerrMay
    • RE: Discrepancy with point positions of Circle Spline Primitive

      Hi @ferdinand,

      thanks for taking the time to clarify that 🙂

      Okay, I see. So one can view this "behavoiur" as intended since it is the result of the math behind how the circle spline is calculated?

      Still I wonder why the circle spline primitive with 1 intermediate point, i.e. with four control points doesn't appear like the n-side spline with 8 sides? Wouldn't it be "cleaner" that way?

      Regading the true values - I am well aware that the repr of c4d.Vector rounds in the GUI. It was only that those values were quite off compared to the values of the points of the n-side spline. That's why my initial question arose. 😉

      Interesting and good to know that rotating points via sine and cosine is slow and no good option. Thank you for that insight. May I ask why that is? I mean why is c4d.utils.MatrixRotZ better? What does that function do differently in terms of performance? In the end it has to do the math as well, does it not?

      Sorry for constantly asking all this stupid questions. It is only that I want to understand these things thoroughly. 😄

      Thanks for your time,
      Sebastian

      posted in Cinema 4D SDK
      H
      HerrMay
    • Discrepancy with point positions of Circle Spline Primitive

      Hi guys,

      to distribute a set of points in a circular manner one can use the following code:

      from typing import Optional
      import c4d
      import math
      
      doc: c4d.documents.BaseDocument  # The active document
      op: Optional[c4d.BaseObject]  # The active object, None if unselected
      
      def main() -> None:
          # Called when the plugin is selected by the user. Similar to CommandData.Execute.
          
          radius = 100.0
          count = 8
          angle: float = (2 * math.pi) / count
          
          points: list[c4d.Vector] = [
              c4d.Vector(radius * math.cos(i * angle), radius * math.sin(i * angle), 0)
              for i in range(count)
          ]
          
          for i, p in enumerate(points):
              print(i, p)
      
      """
      def state():
          # Defines the state of the command in a menu. Similar to CommandData.GetState.
          return c4d.CMD_ENABLED
      """
      
      if __name__ == '__main__':
          main()
      

      Which results in points with the following positions:

      0 Vector(100, 0, 0)
      1 Vector(70.711, 70.711, 0)
      2 Vector(0, 100, 0)
      3 Vector(-70.711, 70.711, 0)
      4 Vector(-100, 0, 0)
      5 Vector(-70.711, -70.711, 0)
      6 Vector(0, -100, 0)
      7 Vector(70.711, -70.711, 0)
      

      We can clearly see that for index 1 the position is Vector(70.711, 70.711, 0)

      So I wonder why this doesn't hold true for a Circle Spline Primitive with uniform intermediate points set to 1? There the position is Vector(70.75, 70.75, 0).

      Is that a bug?

      I have attached a scene file where one can see the difference between a Circle Spline Primitive and a N-Side Spline Primitive.

      Cheers,
      Sebastian

      Circle_Spline_Bug.c4d

      posted in Cinema 4D SDK windows s26 python
      H
      HerrMay
    • RE: How to evenly distribute objects of different 'width'

      Hi @i_mazlov,

      thank you for the explanation as well as the code sample. That really pointed me in the right direction. Especially the projection part along with the topic about bounding box calculations.

      Since GetRad() is oriented with the object I can not really use that for a "bounding box". I then thought I could use the custom class @ferdinand provided here. Well, turns out that this fails as soon as the axis of an object is no longer in the center of the points.

      So I ended up starting at @ferdinand example and building my own bounding box via iterating object points. This finally gave me a bounding box that "always" sits in the "center" of an object - no matter where the axis lives or it that object has "rotated points".

      So far so good. However, I noticed that the code you provided - though meant as a starting point and no optimized implementation - produces different results for the same objects when these objects are rearranged.

      To have a better understanding what I'm talking about, could you be so kind and have a look at the attached scene file?

      You will find that the spacing between objects is not always evenly distributed. I wonder why that is as there is e.g. no scaling involved. Just simple cubes with a different order of positions.

      Thank you for you support.
      Sebastian

      distribute.c4d

      posted in Cinema 4D SDK
      H
      HerrMay
    • How to evenly distribute objects of different 'width'

      Hi guys,

      so, right now I'm at a point where think I give in. After spending hours after hours fiddeling around with matrcies, global and local positions I came to the conclusion I need someones help.

      What I'm trying to achieve.
      I'm currently prototyping a script that evenly distributes objects with potentially different widths. Quite similiar to the align tools known from Photoshop, Illustrator, etc.

      So far I have a solution that works only for objects aligned to the world frame, i.e. as soon as the objects are rotated this works no longer.

      I suspect that I somehow have to translate my computations in relation to the global transform of the objects in question. Right now I'm running out of ideas how to do so.

      I really hope someone can provide some insight here.

      I have attached the scene file as well as a screenshot illustrating the situation. Please find the code I'm using below.

      Cheers,
      Sebastian

      distribute.c4d

      distribute.jpg

      from typing import Optional
      import c4d
      import itertools
      
      doc: c4d.documents.BaseDocument  # The active document
      op: Optional[c4d.BaseObject]  # The active object, None if unselected
      
      def main() -> None:
      
          objs = doc.GetActiveObjects(0)
      
          if not objs:
              return
          
          # Store the x component of selected objs' positions
          positions = [
              obj.GetMg().off.x
              for obj in reversed(objs)
          ]
      
          # Store the x component of selected objs' radius
          radii = [
              obj.GetRad().x
              for obj in reversed(objs)
          ]
      
          # Store the x component of selected objs' diameter
          diameters = [
              radius * 2
              for radius in radii
          ]
      
          # Compute positions so that they sit next to each other without a gap
          stacked = [
              diameter - radius
              for diameter, radius in zip(itertools.accumulate(diameters), radii)
          ]
      
          count = len(radii)
          width = sum(diameters)
          span = min(positions) - radii[0], max(positions) + radii[-1]
          start, end = span
          difference =  end - start - width
          spacing = difference / (count-1)
      
          # Compute the final position for selected objs
          positions = [
              start + position + (spacing * i)
              for i, position in enumerate(stacked)
          ]
      
          print("." * 79)
      
          print(radii)
          print(diameters)
          print(stacked)
          print(width)
          print(span)
          print(spacing)
          print(positions)
      
          print("." * 79)
      
          doc.StartUndo()
      
          # Set the final position computed before
          for obj, posx in zip(reversed(objs), positions):
              doc.AddUndo(c4d.UNDOTYPE_CHANGE, obj)
              mg = obj.GetMg()
              mg.off = c4d.Vector(posx, mg.off.y, mg.off.z)
              obj.SetMg(mg)
      
          doc.EndUndo()
          c4d.EventAdd()
      
      if __name__ == "__main__":
          main()
      
      posted in Cinema 4D SDK python s26 windows
      H
      HerrMay
    • RE: Why do I have to hit Undo twice after performing SendModelingCommand 'Weld'?

      Hi @ferdinand,

      ah okay I see. Some part of me already suspected it could be a bug. Thanks for taking the time to look into it. 🙂

      Regarding the scope of question. You are absolutely right. And while I understand that it can make answering branching questions hard, it is sometimes not that easy to split things up. But all good, no offense taken. 🙂

      One last thing, I know you guys are not here to develop solutions or come up with algorithms. Still, I was wondering if a possible solution could be to move the selected points to a custom vector position and afterwards simply send an "Optimize" modeling command?

      Cheers,
      Sebastian

      posted in Cinema 4D SDK
      H
      HerrMay
    • Why do I have to hit Undo twice after performing SendModelingCommand 'Weld'?

      Hi guys,

      so as the title suggests I observed some strange behaviour of the SMC Weld command in conjunction with BaseObject.GetModelingAxis().

      My test setup is as follows:
      I have a simple plane objects made editable. Then I select some vertices I want to weld together. I then move the Modeling Axis to the random position and execute the script down below.

      The first question I have is why can't I weld the points directly together at the position vector BaseObject.GetModelingAxis() provides me with?

      To circumvent this issue I came up with the workaround to first weld the point selection to its 'center' while afterwards move that point to the position of BaseObject.GetModelingAxis().

      The second question possibly arises from this workaround because while it works it seems that this, way the undo system gets screwed up. To undo the weld operation I have to hit undo twice. Why is that?

      I hope someone can help me here and I could explain my problem well enough. If not just let me know.

      Cheers,
      Sebastian

      from typing import Optional, Generator, Union
      import c4d
      
      doc: c4d.documents.BaseDocument  # The active document
      op: Optional[c4d.BaseObject]  # The active object, None if unselected
      
      
      def is_mesh(node: c4d.BaseObject) -> bool:
          """Return whether *node* is of type *c4d.PointObject*."""
      
          return isinstance(node, c4d.PointObject)
      
      
      def iter_selected_points(obj: c4d.BaseObject) -> Generator:
          """Yield selected points of *obj* along with their index."""
      
          points = obj.GetAllPoints()
          baseselect = obj.GetPointS()
      
          sel = baseselect.GetAll(obj.GetPointCount())
      
          for (index, selected), point in zip(enumerate(sel), points):
              if not selected:
                  continue
              yield index, point
      
      
      def smc_weld(obj: c4d.PointObject, doc: c4d.documents.BaseDocument) -> bool:
          """Perform modeling comannd 'Weld' for selected points of *obj*."""
      
          settings = c4d.BaseContainer()
          # Explicitly say we will not use point, so the center will be used
          settings[c4d.MDATA_WELD_TOPOINT] = False
      
          result = c4d.utils.SendModelingCommand(
              command=c4d.ID_MODELING_WELD_TOOL,
              list=[obj],
              mode=c4d.MODELINGCOMMANDMODE_POINTSELECTION,
              bc=settings,
              doc=doc
          )
      
          return result
      
      
      def smc_optimize(obj: c4d.PointObject, doc: c4d.documents.BaseDocument, tolerance: float = 0.001) -> bool:
          """Perform modeling comannd 'Optimze' for all points of *obj*."""
      
          settings = c4d.BaseContainer()
          settings[c4d.MDATA_OPTIMIZE_TOLERANCE] = tolerance
          settings[c4d.MDATA_OPTIMIZE_POINTS] = True
          settings[c4d.MDATA_OPTIMIZE_POLYGONS] = True
          settings[c4d.MDATA_OPTIMIZE_UNUSEDPOINTS] = True
      
          result = c4d.utils.SendModelingCommand(
              command=c4d.MCOMMAND_OPTIMIZE,
              list=[obj],
              mode=c4d.MODELINGCOMMANDMODE_ALL,
              bc=settings,
              doc=doc
          )
      
          return result
      
      
      def main() -> None:
          # Called when the plugin is selected by the user. Similar to CommandData.Execute.
      
          c4d.StopAllThreads()
      
          doc = c4d.documents.GetActiveDocument()
          doc.StartUndo()
      
          obj = doc.GetActiveObject()
      
          if not obj:
              return
      
          doc.AddUndo(c4d.UNDOTYPE_CHANGE, obj)
          position = obj.GetModelingAxis(doc).off
      
          if not smc_weld(obj, doc):
              msg = f"Could not execute Modeling Command 'Weld'."
              raise RuntimeError(msg)
      
          index = list(dict(iter_selected_points(obj)).keys())[0]
          obj.SetPoint(index, position)
          obj.Message(c4d.MSG_UPDATE)
      
          if not smc_optimize(obj, doc):
              msg = f"Could not execute Modeling Command 'Optimize'."
              raise RuntimeError(msg)
      
          doc.EndUndo()
          c4d.EventAdd()
      
      
      def state():
          # Defines the state of the command in a menu. Similar to CommandData.GetState.
      
          doc = c4d.documents.GetActiveDocument()
          obj = doc.GetActiveObject()
      
          if not obj:
              return False
      
          return c4d.CMD_ENABLED
      
      if __name__ == '__main__':
          main()
      
      posted in Cinema 4D SDK windows s26 python
      H
      HerrMay
    • Why doesn't c4d.Vector.GetAngle throw an error for the null vector?

      Hi guys,

      TL;DR
      Shouldn't c4d.Vector.GetAngle throw a e.g. ZeroDivisionError when invoked on a identity vector, that is a c4d.Vector(0, 0, 0) instead of return 90°?

      For anyone who's interested:
      I'm currently trying to teach myself linear algebra and therefore trying to wrap my head around vectors, its properties, methods and dependencies.

      To get a better understanding I wrote my own Vector class, did research and implemented the common methods as well as the properties that define a vector.

      While I'm doing this I constantly compare my vector class and the outcome of any calculation with the native c4d.Vector to test if my calculations/assumptions hold true.

      When using c4d.Vector.GetAngle with a identity vector, c4d.Vector(0, 0, 0) the method returns 90°. Maybe this is a stupid question but shouldn't it raise an exception that the vector has no direction as well as no length and therefore by definition no angle?

      Cheers,
      Sebastian

      posted in Cinema 4D SDK python s26
      H
      HerrMay
    • RE: Dynamically changing custom icon of TagData Plugin

      Hi @ferdinand,

      alrighty, understood. I know you don't have either the time nor the permission to go on deep bug hunting tours. So no worries there. 🙂

      I stripped down the code to the bare minimum. That way it should be easier.

      Please find below the new project.

      DoNothingTag.zip

      To mimic what I've down please follow these steps. Tested in 2023.2.1

      1. Install the "Do Nothing Tag" Plugin from the ZIP-Archive.

      2. Create some objects. Doesn't matter what kind.

      3. Select all of these objects and apply the "Do Nothing Tag" from the extensions submenu inside the tag menu.

      4. You see the topmost object of the selection gets the red icon while the others get the green one.

      I hope I could be more helpful with these informations. If not, don't hesitate to tell me what I could provide additionally.

      Thank you,
      Sebastian

      posted in Cinema 4D SDK
      H
      HerrMay
    • RE: Dynamically changing custom icon of TagData Plugin

      Hi @ferdinand,

      ah stupid me. I forgot to remove the GetDEnabling part. Didn't mean to bring that along.

      My problem is not so much about enabling or disabling the node(s) but more the fact that some nodes are simply getting the "wrong" icon status when the tag is apllied from the menu.

      Try to comment def CoreMessage(self, id, bc) in the TagTestMessageHandler class and then reload the plugin and apply the tag to your object selection again. You should see that the topmost object gets the red icon while the others get the expected green one.

      Cheers,
      Sebastian

      posted in Cinema 4D SDK
      H
      HerrMay
    • Dynamically changing custom icon of TagData Plugin

      Hi guys,

      I have a tag plugin which makes use of c4d.MSG_GETCUSTOMICON stole the code from here to dynamically change its icon based on a parameter of the tag.

      While this is all fine and dandy, I encountered the problem that this only works if I add the tag to a single object. The minute I have multiple objects selected and add my tag plugin to them, the first (or last, depending how you want to see it) always loads the wrong icon while the other objects get the correct one. See pictures below.

      Wrong behaviour:
      Not_Working.jpg

      Expected behaviour:
      Working.jpg

      My feeling is, that c4d.MSG_MENUPREPARE is the culprit here and only gets called for a single instance of the tag. Not sure though.

      To circumvent this I implemented a c4d.plugins.MessageData plugin which reacts to a c4d.SpecialEventAdd() call I send every time c4d.MSG_MENUPREPARE is called received in the tag plugin. So in essence I delegate the work to the MessageData plugin which is now responsible to set the parameter on the tag plugin instances.

      Long story short - it works. 😄
      But I wonder if this is "the right" way to do it. As it feels a little bit clunky and also somehow overkill for such a small feature. Imagine having a couple of plugins which all implement some similar feature. All of these plugins would then need a MessageData plugin which communicates with them.

      So, to finish this of. Could someone tell me if this is the intended approach or if I#m missing something here and doing things unnecessarily complicated?

      I attached the project so that its easier to follow.

      TagTest.zip

      Cheers,
      Sebastian

      posted in Cinema 4D SDK python s26 windows
      H
      HerrMay
    • RE: Nesting Pivot object causes parent to shoot up into space

      Hi @ferdinand,

      Eureka! Of course, how simple. 😄
      This bevaiour is just what I wanted all long. Man, sometimes you don't see the hand in front of your face.

      Thanks for helping me @ferdinand. You're simply golden.

      P. S. I should really internalize these damn matrices already. 😄

      Cheers,
      Sebastian

      posted in Cinema 4D SDK
      H
      HerrMay
    • RE: Nesting Pivot object causes parent to shoot up into space

      Hi @ferdinand,

      as always - thanks for your explanation. Thats what I thought, I got myself in a race condition there. I feel more and more stupid with these questions I ask. 😄

      However, I wonder what could be a viable solution then? I mean, there must be a better way, right? As one simply can not stop a user from nesting the pivot object under the camera.

      I hacked that rig together because I thought it would be nice to have a tag based solution for an orbit camera. Instead of always nesting various null objects to drive rotations and what not.

      I thought about cloning that pivot into a virtual document and reading its matrix from there and use that as a pivot matrix. Didn't make much difference. The race condition was still there, though not as hefty.

      Cheers,
      Sebastian

      posted in Cinema 4D SDK
      H
      HerrMay
    • Nesting Pivot object causes parent to shoot up into space

      Hello guys,

      I have a python tag that sits on a plane old camera object. The tag has some user data controls for position, rotation etc. along with the option to use a custom pivot object (e.g. c4d.BaseObject(c4d.Onull) ) to rotate the camera around.

      If no custom pivot object is supllied I use the world origin i.e. a unit matrix c4d.Matrix() as the pivot.

      While this all works I noticed some odd behaviour when I nest the custom pivot object as a child object of the camera. Things start to get really weird then. I move the nested pivot object and the camera object starts flying all over the place.

      I suspect that I either ran into some kind of logical limitation or that my math is not correct.

      While I know that my question is probably way beyond support I still wonder if someone could be so kind and spare some time to help me out here.

      Please find below the code I'm using along with the scene file.

      Cheers,
      Sebastian

      Python_OrbitCamera.c4d

      from typing import Optional
      import c4d
      
      doc: c4d.documents.BaseDocument # The document evaluating this tag
      op: c4d.BaseTag # The Python scripting tag
      flags: int # c4d.EXECUTIONFLAGS
      priority: int # c4d.EXECUTIONPRIORITY
      tp: Optional[c4d.modules.thinkingparticles.TP_MasterSystem] # Particle system
      thread: Optional[c4d.threading.BaseThread] # The thread executing this tag
      
      def main() -> None:
          # Called when the tag is executed. It can be called multiple time per frame. Similar to TagData.Execute.
          # Write your code here
      
          pivot = op[c4d.ID_USERDATA,1]
      
          pivot_rotation_heading = op[c4d.ID_USERDATA,3]
          pivot_rotation_pitch = op[c4d.ID_USERDATA,4]
          pivot_rotation_banking = op[c4d.ID_USERDATA,5]
      
          camera_position_x = op[c4d.ID_USERDATA,13]
          camera_position_y = op[c4d.ID_USERDATA,17]
          camera_position_z = op[c4d.ID_USERDATA,18]
          camera_rotation_heading = op[c4d.ID_USERDATA,6]
          camera_rotation_pitch = op[c4d.ID_USERDATA,7]
          camera_rotation_banking = op[c4d.ID_USERDATA,8]
      
          camera = op.GetObject()
      
          if not camera or not camera.CheckType(c4d.Ocamera):
              return
      
          # Get the global matrix of the pivot.
          # If there is no pivot object supplied use the unit matrix.
          pivot_matrix = pivot.GetMg() if pivot is not None else c4d.Matrix()
      
          # Construct a matrix to set our camera object to.
          camera_matrix = (
      
              pivot_matrix *
              c4d.utils.MatrixRotY(pivot_rotation_heading) *
              c4d.utils.MatrixRotX(pivot_rotation_pitch) *
              c4d.utils.MatrixRotZ(pivot_rotation_banking) *
      
              c4d.utils.MatrixMove(
                  c4d.Vector(camera_position_x, camera_position_y, camera_position_z)
              ) *
      
              c4d.utils.MatrixRotY(camera_rotation_heading) *
              c4d.utils.MatrixRotX(camera_rotation_pitch) *
              c4d.utils.MatrixRotZ(camera_rotation_banking)
          )
      
          camera.SetMg(camera_matrix)
      posted in Cinema 4D SDK
      H
      HerrMay
    • RE: How to add custom menu to take manager

      Hi @i_mazlov,

      thank you for your reply. 🙂

      Yes, I thought as much. Too bad the take manager menu is not publicy exposed.

      And while I understand that Maxon as well as the community are not big fans of every plugin messing around with menu insertions, it still is a pitty.

      One can e.g. insert plugins into the menu of the material manager as done so by "CV-Swim".

      IMHO it should pose no problem when done carefully with UX in mind while doing so. Especially when one "only" appends entries to a menu.

      Nethertheless thank you for confirmation.

      Cheers,
      Sebastian

      posted in Cinema 4D SDK
      H
      HerrMay
    • RE: How to get the Interface language of c4d

      Hi @chuanzhen,

      to get a specific installed language via python you can use c4d.GeGetLanguage(index).

      When you want to retrieve all installed langauges you must iterate them yourself.

      See the code example below. Meant to be executed from the script manager.

      Cheers,
      Sebastian

      from typing import Optional, Generator
      import c4d
      
      doc: c4d.documents.BaseDocument  # The active document
      op: Optional[c4d.BaseObject]  # The active object, None if unselected
      
      
      def iter_language(start: int=0) -> Generator[dict, None, None]:
          """Yield all installed languages.
          
          Paramameters 
          ------------
          start
              int: The index to start with
              
          Returns
          -------
              Generator[dict]
              
          Example
          -------
              >>> for language in iter_language():
                      print(language)
                      
              {'extensions': 'en-US', 'name': 'English', 'default_language': True}
          """
      
          while True:
              
              lang = c4d.GeGetLanguage(start)
              
              if lang is None:
                  break
      
              yield lang
              start += 1
      
      
      def main() -> None:
          # Called when the plugin is selected by the user. Similar to CommandData.Execute.
          
          for language in iter_language():
              print(language)
      
      """
      def state():
          # Defines the state of the command in a menu. Similar to CommandData.GetState.
          return c4d.CMD_ENABLED
      """
      
      if __name__ == '__main__':
          main()
      
      posted in Cinema 4D SDK
      H
      HerrMay
    • How to add custom menu to take manager

      Hi guys,

      I wonder if it is possible to add a command plugin via a custom menu to the menu of the take manager? Similar to how one can add a custom menu to the material manager for example.

      Cheers,
      Sebastian

      posted in Cinema 4D SDK
      H
      HerrMay
    • RE: Script: Connect + Delete all subdivision surface objects

      Hi @derekheisler,

      there are multiple reasons why your script isn't catching all of you SDS objects. Number one is that you only take the top level of objects into account. doc.GetObjects() does not account for nested objects that live as children under some other objects. So one solution here is to write a custom function that traverses the complete document i.e. not only the top level objects but every child, sibling, grandchild and so on.

      Number two is in Cinema there is no symbol c4d.Osubdiv. The correct symbol is c4d.Osds. ChatGPT knows and can do a lot, but not everything. 😉

      One additional suggestion. While it can be comfortable to simply use c4d.CallCommand, for proper undo handling you should avoid it and instead use e.g. node.Remove() if you want to delete some object.

      Having said all of that, find below a script that finds all SDS objects gets a copy and of them, bakes them down and inserts the new polygon objects into your document.

      I left out the part to set back the objects position back for you to find out.

      Cheers,
      Sebastian

      import c4d
      
      def get_next(node):
          """Return the next node from a tree-like hierarchy."""
          
          if node.GetDown():
              return node.GetDown()
          
          while not node.GetNext() and node.GetUp():
              node = node.GetUp()
              
          return node.GetNext()
      
      def iter_tree(node):
          """Iterate a tree-like hierarchy yielding every object starting at *node*."""
          
          while node:
              yield node
              node = get_next(node)
              
      def iter_sds_objects(doc):
          """Iterate a tree-like hierarchy yielding every +
          SDS object starting at the first object in the document.
          """
          
          is_sds = lambda node: node.CheckType(c4d.Osds)
          node = doc.GetFirstObject()
          
          for obj in filter(is_sds, iter_tree(node)):
              yield obj
      
      
      def join_objects(op, doc):
          """Join a hierarchy of objects and return them as a single polygon."""
          
          settings = c4d.BaseContainer()
      
          res = c4d.utils.SendModelingCommand(
              command=c4d.MCOMMAND_JOIN,
              list=[op],
              mode=c4d.MODELINGCOMMANDMODE_ALL,
              bc=settings,
              doc=doc
          )
          
          if not isinstance(res, list) or not res:
              return
          
          res = res[0]
          return res.GetClone()
      
      
      # Main function
      def main():
          
          doc.StartUndo()
          
          null = c4d.BaseObject(c4d.Onull)
          
          tempdoc = c4d.documents.BaseDocument()
          tempdoc.InsertObject(null)
          
          for sds in iter_sds_objects(doc):
              clone = sds.GetClone()
              clone.InsertUnderLast(null)
              
          joined = join_objects(null, tempdoc)
          
          doc.InsertObject(joined)
          doc.AddUndo(c4d.UNDOTYPE_NEW, joined)
          
          doc.EndUndo()
          c4d.EventAdd()
      
      # Execute main()
      if __name__=='__main__':
          main()
      
      posted in Cinema 4D SDK
      H
      HerrMay