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. John_Do
    3. Posts
    • Profile
    • Following 0
    • Followers 0
    • Topics 9
    • Posts 33
    • Best 1
    • Controversial 0
    • Groups 0

    Posts made by John_Do

    • RE: Finding duplicate materials - Octane Render

      Hi, sorry for bringing back up this topic but the Compare() method still gives weird results in 2024.5.1.

      Cinema_4D_D5fWj9O4RQ.gif

      • Comparing a material duplicated with Ctrl-Drag returns sometimes True, sometimes False
      • Comparing a material against a copy-pasted version (in the same scene) always returns False
      • Comparing two identical materials both copy-pasted together in one-go in another scene always returns False, even if True was returned in the original scene.

      Please note that I'm using Corona Materials here but results are the same with the Standard Material.

      posted in Cinema 4D SDK
      John_DoJ
      John_Do
    • CopyBitmapToClipboard gamma issue

      Hello,

      I'm writing a script to render objects in a specific document and copy the rendered image into the clipboard. Rendered image is fine, but
      the one pasted from the clipboard miss a gamma transform, it's darker.
      I've read about color profiles here and played a bit with it but it doesn't change the result. Any tip to fix that?

      abe57d25-112d-4f5d-adea-b7ff5c44320b-image.png

      Color Management is in Basic Mode with LWF enabled, and I'm rendering with Corona, no OCIO involved.

      Thanks !

      posted in Cinema 4D SDK python 2024 windows
      John_DoJ
      John_Do
    • RE: Copy info from one Object to other Object

      @Manuel Thanks ! Still here in 2024.5.1

      posted in Cinema 4D SDK
      John_DoJ
      John_Do
    • RE: CommandData.Message() implementation and Message concepts

      Hi @ferdinand,

      As always thank you very much for the detailed answer. Please mind that I'm not a seasoned programmer at all, not even a programmer. That doesn't excuse anything, but there are likely many mistakes in my messages.

      Thank you for reaching out to us. I must point out that your question is out of scope of support because you are using Corona. Thrid party libraries are out of scope of support as declared in our Forum Procedures. Please understand that we cannot run, debug, or help you with foreign APIs.

      I know that Corona is out of the scope of this forum since I've already posted some questions about it, and we already discussed about it. But the objects and data particular to Corona are based on the class and methods of the Cinema 4D API as far as I can tell. If it is still out of scope let me know. I've already mailed the Corona devs and had a bit of help few years ago but that's all. I made a diagram recently that shows how the Node Editor is integrated through the API.

      Messages
      Thanks for the explanation regarding messages, but I still don't get it. I think it doesn't make sense to me that I have to tell Cinema something has changed since something has really changed ( = through the interpreted Python code) from my point of view when executing the code. My lack of programming background maybe doesn't help me here.
      So in most cases, messages are used to trigger UI events / updates?
      How do I know if I have to send a message or not to an object to update it ( or it's representation in the current document )?
      How should I leverage CommandData.Message() and what is the difference with calling C4DAtom.Message / SendCoreMessage in Execute ? I'm really puzzled here.

      What is more likely going wrong here, is how you assemble your shader graph. For your first plugin, this little stretch of code is trying to do a lot all at once. And especially when I would run into problems, I would try to reduce that. In play comes here then how the Corona API works. It is probably best when you ask the Corona team for help with that.

      It's quite possible, but the thing is the code do exactly what I want (inserting a filter shader on top of any selected shader). But maybe I take too many shorcuts or that things are done in a unorthodox way. What would be the proper way to do this ? Split what's happening in Execute into multiple methods ?

      ¹ An added event will not be carried out right away but when the event queue is resolved. I.e., having one EventAdd at the end of your operation is equivalent to having multiple calls in one operation. EventAdd will only be carried out once you give control back to Cinema 4D.

      Is it a problem ? The command is really simple and linear, really it's just a script wrapped in a class' method.

      CommandData plugins also call EventAdd on their own after their Execute ran, so you do not have to do it yourself.

      I suspected that but I don't get the same result if I comment out EventAdd. Ui doesn't update and links are missing on the node widgets. So what's happening here ?

      alt text
      alt text
      alt text

      posted in Cinema 4D SDK
      John_DoJ
      John_Do
    • CommandData.Message() implementation and Message concepts

      Hi,

      I'm writing my first plugin and I have a hard time understanding how to implement the Message method of the CommandData class.

      The command is straightforward: it creates new shaders and the corresponding widgets for the Corona Node Material Editor. So in essence all the command is doing is modifying BaseList2D objects (shaders and node widgets) which are parented to another BaseList2D object (the Node Editor view).
      In this case, using EventAdd() refresh the Node Editor view instantly and populate it with new nodes, shaders, and links. On the other hand, using C4DAtom.Message() gives partial results = broken objects and links. And for the Message method, I just don't get it, even after looking at the docs and the example plugins extensively.

      class AddFilterShader(c4d.plugins.CommandData):
      
          def Execute(self, doc):
      
      
              context = CoronaNodeContext()
              nodes = context.active_nodes
              active_view = context.active_view
      
      
              if nodes:
      
                  parent_shader = set()
      
                  for node in nodes:
                      # Get the shader represented by the node widget
                      node_shader = node[2001]
      
                      # Skip if its a material
                      if isinstance(node_shader, c4d.BaseMaterial):
                          c4d.StatusSetText("Can't run on a material")
                          continue
      
                      # Filter shader
                      filter = c4d.BaseShader(c4d.Xfilter)
                      filter[c4d.SLA_FILTER_TEXTURE] = node_shader
      
                      # We check for a parent node, if not
                      # we parent to the Corona Node shaderhook
                      parent = node_shader.GetUp()
                      if parent is None:
                          parent = node_shader.GetMain()
                      # Add the parent for further operations
                      parent_shader.add(parent)
      
                      # Look for the link in the parent
                      if isinstance(parent,c4d.BaseMaterial) or isinstance(parent, c4d.BaseShader) :
                          parent_bc = parent.GetDataInstance()
                          index = bc_get_id(parent_bc, node_shader)
                          parent_bc.SetLink(index, filter)
      
                      # Create a new widget for the shader
                      cnode = create_node_widget(filter)
      
                      # Insert shaders
                      node_shader.InsertUnder(filter)
      
                      if isinstance(parent,c4d.BaseMaterial):
                          parent.InsertShader(filter)
                      else:
                          filter.InsertUnder(parent)
      
                      # Insert widget
                      cnode.InsertUnder(active_view)
      
                      # Selection state
                      node.DelBit(c4d.BIT_ACTIVE)
                      cnode.SetBit(c4d.BIT_ACTIVE)
      
                      # Update the node and the shaders
                      # Doesn't work ? 
                      node.Message(c4d.MSG_UPDATE)
                      parent.Message(c4d.MSG_UPDATE)
      
              # Update the view
              # Doesn't work ? 
              active_view.Message(c4d.MSG_UPDATE)
      
              c4d.EventAdd()
      
              return True
      

      As you can see I tried the C4DAtom.Message() command inside the Execute method but it didn't get any better.

      So how should I use the Message method to get the same result as with EventAdd()? Or should I not bother and use the function? But even so, I'd like to understand the Message concept.

      Thanks !

      posted in Cinema 4D SDK python 2023
      John_DoJ
      John_Do
    • RE: Copy children to selection with GetClone()

      Hi @ferdinand, thanks for the feedback, I've got it working with your suggestion. The method B was correct but the undo part was wrong ( the undo step was applied on the original objects instead of the new ones, thus leading to a mess when performing an undo). I also added the bit part to keep new objects folded in the OM. Here is the code :

      # CopyDeformers
      
      import c4d
      
      doc = c4d.documents.GetActiveDocument()
      
      
      def main():
      
          doc.StartUndo()
      
          objs = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_SELECTIONORDER)
      
          if len(objs) >= 2:
              # Get the last selected object
              active = objs.pop(-1)
      
              # Get the deformers
              deformers = [i for i in active.GetChildren()]
      
              # Copy deformers to selection
              if deformers:
                  for o in objs:
                      for d in deformers[::-1]:
                          dcopy = d.GetClone(c4d.COPYFLAGS_NO_BITS)
                          doc.InsertObject(dcopy, parent=o)
                          doc.AddUndo(c4d.UNDOTYPE_NEW, dcopy)
                      o.SetBit(c4d.BIT_MFOLD)
      
          doc.EndUndo()
      
          c4d.EventAdd()
      
      
      if __name__ == '__main__':
          main()
      
      
      posted in Cinema 4D SDK
      John_DoJ
      John_Do
    • Copy children to selection with GetClone()

      Hi all,

      I'm writing a script to copy the children objects of an "active" object to other selected objects using the GetClone() function, but I can't get it to work as intended, I feel I'm missing something obvious. The intent is to copy deformers from the active object to all others (but I don't want to limit the function to deformers).

      import c4d
      
      doc = c4d.documents.GetActiveDocument()
      
      
      def main():
      
          doc.StartUndo()
      
          objs = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_SELECTIONORDER)
      
          if len(objs) >= 2:
              # Get the last selected object
              active = objs.pop(-1)
      
              # --------------------------------- Method A --------------------------------- #
      
              # Get the deformers
              deformers = [i.GetClone(c4d.COPYFLAGS_NO_BITS) for i in active.GetChildren()]
      
              # Copy deformer to selection
              if deformers:
                  for o in objs:
                      for d in deformers[::-1]:
                          doc.InsertObject(d, parent=o)
                          doc.AddUndo(c4d.UNDOTYPE_NEW, d)
      
              # --------------------------------- Method B --------------------------------- #
      
              # # Get the deformers
              # deformers = [i for i in active.GetChildren()]
      
              # Copy deformer to selection
              # if deformers:
              #     for o in objs:
              #         for d in deformers[::-1]:
              #             doc.InsertObject(d.GetClone(c4d.COPYFLAGS_NO_BITS), parent=o)
              #             doc.AddUndo(c4d.UNDOTYPE_NEW, d)
      
          doc.EndUndo()
      
          c4d.EventAdd()
      
      
      if __name__ == '__main__':
          main()
      
      

      The method A kinda works but do only one copy and stop without error. I didn't see anything meaningful after printing the lists content in various steps of the script, everything seems ok from my understanding.
      The method B kinda works too and successfully copy deformers on all the selected objects but the undo is very buggy ( multiple steps rather than one as intended, delete deformers on the active object).

      Also the newly inserted objects open the hierarchies in the OM, is it because it's the default behavior of C4D when creating child objects ? Do I have to toggle BIT_OFOLD manually ?

      Thanks !

      posted in Cinema 4D SDK python 2024
      John_DoJ
      John_Do
    • RE: Undo method for LayerShaderLayer

      Oof, that's an unfortunate end for me but at least I have a clear explanation. Thanks @ferdinand

      posted in Bugs
      John_DoJ
      John_Do
    • RE: Undo method for LayerShaderLayer

      Hi @ferdinand , have you got a chance to look at the issue from the Cinema 4D side ?

      posted in Bugs
      John_DoJ
      John_Do
    • RE: Undo method for LayerShaderLayer

      Hi @ferdinand ,

      Thank you so much for taking the time to test all of this. Out of curiosity I tried your code on my side, and the same thing happen on native Cinema 4D materials.

      Please let me know if you find the root cause of the bug, in the meantime I'll publish the script with all necessary warnings.

      Have a nice day

      posted in Bugs
      John_DoJ
      John_Do
    • RE: Undo method for LayerShaderLayer

      Hey @ferdinand ,

      Sorry, full code below !

      layer.SetParameter(...) returns True for each call as the assignation works. The code did not raise any error or exception. But if I undo/redo, bitmap shaders disappears, Corona ones as well as the previous Cinema ones. It seems that only Layer Shader's layers remain, but with a missing/broken shader link ?

      alt text

      # CT_C4DBitmapToCoronaBitmap
      
      import c4d
      
      
      def iter_shaders(material: c4d.BaseMaterial, mask: int = None) -> list:
      
          shaders = []
      
          shader = material.GetFirstShader()
      
          if shader == None:
              return
      
          while shader:
      
              if mask:
                  if shader.GetType() == mask:
                      shaders.append(shader)
              else:
                  shaders.append(shader)
      
              if shader.GetDown():
                  shader = shader.GetDown()
              elif shader.GetNext():
                  shader = shader.GetNext()
              else:
                  while shader.GetUp():
                      if shader.GetUp().GetNext():
                          shader = shader.GetUp().GetNext()
                          break
                      shader = shader.GetUp()
                  else:
                      shader = None
      
          return shaders
      
      
      def get_layershader_layer(doc: c4d.documents.BaseDocument, layer_shader: c4d.BaseShader, target_shader: c4d.BaseShader = None) -> c4d.LayerShaderLayer:
          # Iterating over Layer Shader layers, looking
          # for the specified type of shaders
          if layer_shader.GetType() == c4d.Xlayer:
              layer = layer_shader.GetFirstLayer()
              while layer != None:
                  if layer.GetType() == c4d.TypeShader:
                      shader = layer.GetParameter(c4d.LAYER_S_PARAM_SHADER_LINK)
                      if target_shader and shader == target_shader:
                          return layer
                      layer = layer.GetNext()
                  elif layer.GetNext():
                      layer = layer.GetNext()
                  else:
                      layer = None
      
      
      def convert_c4d_bitmap(doc: c4d.documents.BaseDocument, c4d_bitmap: c4d.BaseShader, remove=True):
          cr_bitmap = create_cr_bitmap_shader(
              c4d_bitmap[c4d.BITMAPSHADER_FILENAME], c4d_bitmap[c4d.BITMAPSHADER_COLORPROFILE], c4d_bitmap[c4d.ID_BASELIST_NAME], c4d_bitmap[c4d.BITMAPSHADER_EXPOSURE], c4d_bitmap)
          cr_bitmap_added = True
          if c4d_bitmap.GetUp():
              parent = c4d_bitmap.GetUp()
              cr_bitmap.InsertUnder(parent)
              doc.AddUndo(c4d.UNDOTYPE_NEW, cr_bitmap)
              if parent.CheckType(c4d.Xlayer):
                  layer = get_layershader_layer(doc, parent, c4d_bitmap)
                  cr_bitmap[c4d.ID_BASELIST_NAME] = layer.GetName(doc)
                  doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, parent)
                  layer.SetParameter(c4d.LAYER_S_PARAM_SHADER_LINK, cr_bitmap)
              else:
                  parent_shader_slot = get_bc_id(
                      parent.GetDataInstance(), c4d_bitmap)
                  doc.AddUndo(c4d.UNDOTYPE_CHANGE, parent)
                  parent[parent_shader_slot] = cr_bitmap
          elif c4d_bitmap.GetMain():
              parent = c4d_bitmap.GetMain()
              doc.AddUndo(c4d.UNDOTYPE_CHANGE, parent)
              parent.InsertShader(cr_bitmap)
              doc.AddUndo(c4d.UNDOTYPE_NEW, cr_bitmap)
              parent_shader_slot = get_bc_id(parent.GetDataInstance(), c4d_bitmap)
              parent[parent_shader_slot] = cr_bitmap
          else:
              cr_bitmap_added = False
      
          # Removing the old bitmap
          if remove and cr_bitmap_added:
              doc.AddUndo(c4d.UNDOTYPE_DELETEOBJ, c4d_bitmap)
              c4d_bitmap.Remove()
          return cr_bitmap
      
      
      def create_cr_bitmap_shader(filepath: str, colorspc: int, basename: str, exposure: float, shader):
          cr_bitmap = c4d.BaseShader(1036473)
          cr_bitmap_bc = cr_bitmap.GetDataInstance()
          cr_bitmap_bc.SetFilename(c4d.CORONA_BITMAP_FILENAME, filepath)
          cr_bitmap_bc.SetInt32(c4d.CORONA_BITMAP_COLORPROFILE, colorspc)
          cr_bitmap_bc.SetString(c4d.ID_BASELIST_NAME, basename)
          cr_bitmap_bc.SetFloat(c4d.CORONA_BITMAP_EXPOSURE,
                                exposure if colorspc in [1, 3] else exposure * 2.2)
          return cr_bitmap
      
      
      def get_bc_id(target_bc, target_value):
          for index, value in target_bc:
              if value == target_value:
                  return index
      
      
      def main():
      
          doc = c4d.documents.GetActiveDocument()
      
          materials = doc.GetActiveMaterials()
      
          doc.StartUndo()
      
          # Main loop
          for material in materials:
      
              # Step over non Corona Physical materials or Corona Legacy materials.
              if material.GetType() not in (1056306, 1032100):
                  continue
      
              for bitmap_shader in iter_shaders(material, c4d.Xbitmap):
                  convert_c4d_bitmap(doc, bitmap_shader)
      
          doc.EndUndo()
      
          c4d.EventAdd()
      
      
      if __name__ == '__main__':
          main()
      
      
      posted in Bugs
      John_DoJ
      John_Do
    • RE: Undo method for LayerShaderLayer

      Hi @ferdinand,

      Thank you for the sample code ! Indeed I did not provide my code to avoid a complicated request in case I would have missed something really obvious.

      I guessed that your answer was the way to go and already tried it before creating the topic, unfortunately it doesn't work. The good news is it should, so something else must be wrong in my code. You'll find the problematic part below, the part with the Layer Shader layer is the first case in the convert_c4d_bitmap() function :

      def get_layershader_layer(doc: c4d.documents.BaseDocument, layer_shader: c4d.BaseShader, target_shader: c4d.BaseShader = None) -> c4d.LayerShaderLayer:
          # Iterating over Layer Shader layers, looking
          # for the specified type of shaders
          if layer_shader.GetType() == c4d.Xlayer:
              layer = layer_shader.GetFirstLayer()
              while layer != None:
                  if layer.GetType() == c4d.TypeShader:
                      shader = layer.GetParameter(c4d.LAYER_S_PARAM_SHADER_LINK)
                      if target_shader and shader == target_shader:
                          return layer
                      layer = layer.GetNext()
                  elif layer.GetNext():
                      layer = layer.GetNext()
                  else:
                      layer = None
      
      
      def convert_c4d_bitmap(doc: c4d.documents.BaseDocument, c4d_bitmap: c4d.BaseShader, remove=True):
          cr_bitmap = c4d.BaseShader(1036473)
          cr_bitmap_added = True
          if c4d_bitmap.GetUp():
              parent = c4d_bitmap.GetUp()
              cr_bitmap.InsertUnder(parent)
              doc.AddUndo(c4d.UNDOTYPE_NEW, cr_bitmap)
      
              # if parent is a layer shader
              if parent.CheckType(c4d.Xlayer):
                  layer = get_layershader_layer(doc, parent, c4d_bitmap)
                  cr_bitmap[c4d.ID_BASELIST_NAME] = layer.GetName(doc)
                  doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, parent)
                  layer.SetParameter(c4d.LAYER_S_PARAM_SHADER_LINK, cr_bitmap)
                  
              # if parent is another type shader (i.e. Fusion Shader)
              else:
                  parent_shader_slot = get_bc_id(
                      parent.GetDataInstance(), c4d_bitmap)
                  doc.AddUndo(c4d.UNDOTYPE_CHANGE, parent)
                  parent[parent_shader_slot] = cr_bitmap
      
          # if parent is a material
          elif c4d_bitmap.GetMain():
              parent = c4d_bitmap.GetMain()
              doc.AddUndo(c4d.UNDOTYPE_CHANGE, parent)
              parent.InsertShader(cr_bitmap)
              doc.AddUndo(c4d.UNDOTYPE_NEW, cr_bitmap)
              parent_shader_slot = get_bc_id(parent.GetDataInstance(), c4d_bitmap)
              parent[parent_shader_slot] = cr_bitmap
          else:
              cr_bitmap_added = False
      
          # Removing the old bitmap
          if remove and cr_bitmap_added:
              doc.AddUndo(c4d.UNDOTYPE_DELETEOBJ, c4d_bitmap)
              c4d_bitmap.Remove()
          return cr_bitmap
      
      
      def get_bc_id(target_bc, target_value):
          for index, value in target_bc:
              if value == target_value:
                  return index
      
      
      

      Thank you

      posted in Bugs
      John_DoJ
      John_Do
    • Undo method for LayerShaderLayer

      Hi,

      Following the work on this script, I 've now added the support for Layer Shader. Works fine unless for the undo : AddUndo() doesn't work on LayerShaderLayer objects since it isn't a child class of BaseList2D. So I can't add an undo step for the parameters changes on the Layer Shader' layers and it effectively breaks when performing the undo, leaving the Layer Shader with empty layers. What can I do about this ?

      Thank you

      posted in Bugs python 2024 limitation
      John_DoJ
      John_Do
    • RE: TypeError: GeListNode_ass_subscript when creating a new object

      Hi Maxime,

      Thank you very much for looking at my issue.
      Did you try the new function ? I've added it in my code and unfortunately I get the same result as before.

      Cinema_4D_3JwcCopDUD.gif

      posted in Cinema 4D SDK
      John_DoJ
      John_Do
    • RE: TypeError: GeListNode_ass_subscript when creating a new object

      Hi Maxime,

      Thank for looking into this, I'll send you my test scene by DM since it contains some 3rd party textures.

      posted in Cinema 4D SDK
      John_DoJ
      John_Do
    • RE: TypeError: GeListNode_ass_subscript when creating a new object

      Hi Maxime,

      It's a script for Corona for Cinema 4D so I don't know if it'll be of any use to you. But from what I've understood materials and shaders structure are identical to the Standard C4D Material, so I've reduced the script to its essentials by removing few parameters, code below.

      import c4d
      import time
      
      
      
      def iter_shaders(node):
          """Credits go to @ferdinand and @HerrMay over 
          at https://developers.maxon.net/ for the tree-traversal method
      
          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.
          visited = {}
          terminator = node
      
          while node:
      
              if isinstance(node, c4d.BaseMaterial) and not node.GetFirstShader():
                  break
      
              if isinstance(node, c4d.BaseMaterial) 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 visited.get(bytes(node_uuid)):
                  yield node
                  visited[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 visited.get(bytes(child_uuid)):
                  node = child
      
              elif node == terminator:
                  break
      
              elif node.GetNext():
                  node = node.GetNext()
      
              else:
                  node = node.GetUp()
      
      
      def convert_c4d_bitmap(doc, c4d_bitmap, remove=True):
          # Create the bitmap object in memory
          cr_bitmap = create_cr_bitmap_shader(
              c4d_bitmap[c4d.BITMAPSHADER_FILENAME])
          if isinstance(c4d_bitmap.GetUp(), c4d.BaseShader):
              parent = c4d_bitmap.GetUp()
              # Looking for the slot in the parent base
              # container where the shader is inserted
              parent_shader_slot = get_bc_id(parent.GetDataInstance(), c4d_bitmap)
              cr_bitmap.InsertUnder(parent)  # Insert the bitmap in the shader
              doc.AddUndo(c4d.UNDOTYPE_NEW, cr_bitmap)
              doc.AddUndo(c4d.UNDOTYPE_CHANGE, parent)
              # Linking the bitmap in the right slot
              parent[parent_shader_slot] = cr_bitmap
          elif isinstance(c4d_bitmap.GetMain(), c4d.BaseMaterial):
              parent = c4d_bitmap.GetMain()
              parent_shader_slot = get_bc_id(parent.GetDataInstance(), c4d_bitmap)
              parent.InsertShader(cr_bitmap)
              doc.AddUndo(c4d.UNDOTYPE_NEW, cr_bitmap)
              doc.AddUndo(c4d.UNDOTYPE_CHANGE, parent)
              parent[parent_shader_slot] = cr_bitmap
          # Removing the old bitmap
          if remove:
              doc.AddUndo(c4d.UNDOTYPE_DELETEOBJ, c4d_bitmap)
              c4d_bitmap.Remove()
          return cr_bitmap
      
      
      def create_cr_bitmap_shader(filepath):
          cr_bitmap = c4d.BaseShader(1036473)
          cr_bitmap_bc = cr_bitmap.GetDataInstance()
          cr_bitmap_bc.SetFilename(c4d.CORONA_BITMAP_FILENAME, filepath)
          return cr_bitmap  # Return the bitmap shader object for further material insertion
      
      
      def get_bc_id(target_bc, target_value):
          for index, value in target_bc:
              if value == target_value:
                  return index
      
      
      def main():
      
          doc = c4d.documents.GetActiveDocument()
      
          materials = doc.GetActiveMaterials()
      
          doc.StartUndo()
      
          # Performance measurements
          tic = time.perf_counter()
          c = 0
      
          # Main loop
          for material in materials:
      
              mtl_shaders = []
      
              # 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):
                  if shader.GetRealType() == c4d.Xbitmap:
                      mtl_shaders.append(shader)
      
              if mtl_shaders:
                  for i in mtl_shaders:
                      c += 1
                      convert_c4d_bitmap(doc, i)
      
              print(end="\n")
      
          # Performance measurements
          toc = time.perf_counter()
          print(
              f"Bitmap conversion successful: {c} shaders converted in {toc - tic:0.4f} seconds ")
      
          doc.EndUndo()
      
          c4d.EventAdd()
      
      
      if __name__ == '__main__':
          main()
      

      One thing worth mentionning : with this version, I've got the also a TypeError but the Traceback is different, now the AddUndo method raises an exception.

      Traceback (most recent call last):
        File "scriptmanager", line 162, in <module>
        File "scriptmanager", line 147, in main
        File "scriptmanager", line 99, in convert_c4d_bitmap
      SystemError: <method 'AddUndo' of 'c4d.documents.BaseDocument' objects> returned a result with an exception set
      

      Thank you

      posted in Cinema 4D SDK
      John_DoJ
      John_Do
    • TypeError: GeListNode_ass_subscript when creating a new object

      Hello,

      I have the following error message in one of my script ( which is iteraring over shaders) when I perfom these steps :

      1. running the script over the materials
      2. performing an undo
      3. running the script again > error
      TypeError: GeListNode_ass_subscript expected Description identifier, not None
      
      The above exception was the direct cause of the following exception:
      
      Traceback (most recent call last):
        File "scriptmanager", line 198, in <module>
        File "scriptmanager", line 171, in main
        File "scriptmanager", line 84, in convert_c4d_bitmap
        File "scriptmanager", line 116, in create_cr_bitmap_shader
      SystemError: <class 'c4d.BaseShader'> returned a result with an exception set
      

      I've seen another topic where the same error was reported, but in this case the error is happening on the line responsible for creating a new BaseShader object ( first line of the function below ). Undo / Redo commands are working perfectly fine, so I really don't see how I could fix this one.

      def create_cr_bitmap_shader(filepath, colorspc, basename, exposure, shader):
          cr_bitmap = c4d.BaseShader(1036473)
          cr_bitmap_bc = cr_bitmap.GetDataInstance()
          cr_bitmap_bc.SetFilename(c4d.CORONA_BITMAP_FILENAME, filepath)
          cr_bitmap_bc.SetInt32(c4d.CORONA_BITMAP_COLORPROFILE, colorspc)
          cr_bitmap_bc.SetString(c4d.ID_BASELIST_NAME, basename)
          cr_bitmap_bc.SetFloat(c4d.CORONA_BITMAP_EXPOSURE, exposure)
          return cr_bitmap  # Return the bitmap shader object for further material insertion
      

      The result of the error is a messed up shader tree, with some shaders missing file path, others completely removed from the scene.

      Thank you

      posted in Cinema 4D SDK python 2024
      John_DoJ
      John_Do
    • RE: Issue collecting all the shaders in a material

      Hi,

      One last thing I've noticed.

      The last code shared by @HerrMay works great on all materials but the one I'm using to test my script, it's odd.
      On this particular material, the function consistently skip the shader which is in the Blend Channel of the Fusion shader. Even the Mask shader in the Mask channel is discovered and printed.
      db2bf693-59e6-4eb1-a42e-5c484b38f253-image.png

      I've ran the debugger on the function :

      • when the loop state is on the Color shader, the function is going up on the parent Fusion shader as expected
      • but then the elif node.GetNext() condition isn't verified (?!) so it skips the last branch and go back to the material

      The thing is it doesn't happen with other materials nor other shaders :

      • I've made this particular material's shader tree more complex and all the shaders are seen, even when a node has several children.
      • A brand new material with the same shader setup is not a problem.

      There are two cases where the function scan the shader correctly and output the expected result :

      • When using the recursive function written by @ferdinand
      • Disconnecting and reconnecting the Filter shader from the Fusion shader. If I run the function again after doing that, the AO and Filter shaders are discovered and printed correctly.

      Do you have any idea of what it is happening here ?
      I'm planning to use this function for several commands and I don't like the possibility that it could fail in some cases, even if it is some niche ones.

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

      @m_adam Thanks it's a nice addition, I will look into this

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

      Thank you for the script @HerrMay it works great on a Cinema 4D Standard Material !

      Without recursion I find this one easier to understand, I just had to change the class check to from c4d.Material to c4d.BaseMaterial to made it working with Corona.

      One thing though I'm not sure to understand is the terminator thing, with this in the function :

      terminator = node
      

      and this a little further in the loop

              elif node == terminator:
                  break
      

      Since the terminator assignation is done out of the loop, does it mean it is equal to the material in this case, so when the loop is getting back to the first assigned element = the material, it ends ?

      posted in Cinema 4D SDK
      John_DoJ
      John_Do