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. Manuel
    3. Best
    • Profile
    • Following 0
    • Followers 2
    • Topics 1
    • Posts 1,281
    • Best 287
    • Controversial 0
    • Groups 0

    Best posts made by Manuel

    • RE: Add/Remove Groups of User Data

      Hello,

      After some research in the forum, i've found this post that show how to react to a button pressed.

      With that, it juste a matter of using AddUserData and his friend RemoveUserData

      Let me try to explain a bit more what i've done.
      In the UserData tab, i've a got a group that will be used as a container, so i can add or remove thing inside without touching other userdata. I just browse through all user data using GetUserDataContainer and check the parent of the fields with DESC_PARENTGROUP

      I'm pretty sure you will go through this code easily but if you got any questions, feel free to ask.

      i'm adding the file so you can play with the code directly
      AddRemoveGroup.c4d

      And the code. At the top, i've got the ID of both button and the group that will serve as a container.

      import c4d
      
      
      # the group id where all the fiels will be created.
      addButtonID = 1
      removeButtonID = 2
      mainGroupID  = 3
      
      
      
      def AddGroupOfUserData():
          """
          Creating the group with differents field.
          Called by button Add
          """
          obj = op.GetObject() # get the object where we want the user data
          parentGroup = obj.GetUserDataContainer()[mainGroupID][0]
          newGroup = CreateGroup(parentGroup)
      
          nameString = AddString(newGroup, "Name")
          check = AddBool (newGroup, "Use Sound")
          timeField = AddTime(newGroup, "StartTime")
          AddSoundPath(newGroup, "Sound")
          
          #as function are returning the id of the userdata we can change their value
          obj[nameString] = "Default value for the string"
          #Set the bool to tru by default
          obj[check] = True
          #set the time to 0 
          obj[timeField] = c4d.BaseTime(0)
      
      def RemoveGroupOfUserData():
          """
          this will iterate trough all group and remove the last one
          including his children
          """
          obj = op.GetObject() # get the object where we want the user data
          lastGroupID, count = GetLastGroupID()
      
      
          if lastGroupID > -1:
              # we find a group so let remove all the child of that group we found
              for descID, bc in op.GetObject().GetUserDataContainer():
      
                  if IsChildOfGroup(bc, lastGroupID):
                     obj.RemoveUserData(descID)
                     del(obj[descID]) #remove the information in the basecontainer
      
              obj.RemoveUserData(lastGroupID)
              del(obj[lastGroupID])
              obj.Message(c4d.MSG_UPDATE)
      
      
      def IsChildOfGroup(bc, groupID = mainGroupID):
          """
          Check if the parent is the same id of groupID
          """
          if bc[c4d.DESC_PARENTGROUP].GetDepth() > 1:
              if bc[c4d.DESC_PARENTGROUP][1].id == groupID:
                  return True
          return False
      
      
      def GetLastGroupID(parent = None):
          """
          get the last group in the user data
          """
          groupID = -1
          count = 0
          for descID, bc in op.GetObject().GetUserDataContainer():
              if descID[1].dtype == c4d.DTYPE_GROUP:
                  if IsChildOfGroup(bc):
                          count += 1
                          groupID = descID[1].id
      
          return groupID, count
      
      
      def CreateGroup(parent, trackNumber = -1):
          """
          this will create a group in the user data below "myGroup" with all the different data needed
          """
          if trackNumber < 0:
              # we must calculate the track number
              lastGroupID, trackNumber = GetLastGroupID()
      
          #Get the default container for the group type
          bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_GROUP)
          longName  = "Track " + str(trackNumber)
          shortName = "Track"  + str(trackNumber)
      
          bc[c4d.DESC_NAME] = longName
          bc[c4d.DESC_SHORT_NAME] = shortName
          bc[c4d.DESC_TITLEBAR] = True
          bc[c4d.DESC_GUIOPEN] = False
          bc[c4d.DESC_PARENTGROUP] = parent
          #return the group id added so we can add other element to it
          return op.GetObject().AddUserData(bc)
      
      
      
      def AddBool (group, name):
          """
          this will create a boolean gadget in the group
          """
          bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_BOOL)
          bc[c4d.DESC_NAME] = name
          bc[c4d.DESC_SHORT_NAME] = name
          bc[c4d.DESC_PARENTGROUP] = group
          return op.GetObject().AddUserData(bc)
      
      
      def AddTime(group, name):
          """
          this will add the field start time for exemple
          """
          bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_TIME)
          bc[c4d.DESC_NAME] = name
          bc[c4d.DESC_SHORT_NAME] = name
          #bc[c4d.DESC_UNIT] = c4d.DESC_UNIT_TIME
          bc[c4d.DESC_PARENTGROUP] = group
          return op.GetObject().AddUserData(bc)
      
      def AddSoundPath(group, name):
          """
          this will add the field for file
          """
          bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_FILENAME)
          bc[c4d.DESC_NAME] = name
          bc[c4d.DESC_SHORT_NAME] = name
          bc[c4d.DESC_PARENTGROUP] = group
          return op.GetObject().AddUserData(bc)
      
      
      def AddString(group, name):
          """
          this will add a static string to a group
          """
          bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_STATICTEXT)
          bc[c4d.DESC_NAME] = name
          bc[c4d.DESC_SHORT_NAME] = name
          #bc[c4d.DESC_CUSTOMGUI] = c4d.CUSTOMGUI_STATICTEXT
          bc[c4d.DESC_PARENTGROUP] = group
          return op.GetObject().AddUserData(bc)
      
      
      
      def message(msg_type, data) :
          if msg_type == c4d.MSG_NOTIFY_EVENT:
              event_data = data['event_data']
              if event_data['msg_id'] == c4d.MSG_DESCRIPTION_COMMAND:
                  desc_id = event_data['msg_data']['id']
                  if desc_id[1].id == addButtonID:
                      AddGroupOfUserData()
                  elif desc_id[1].id == removeButtonID:
                      RemoveGroupOfUserData()
                      
      
      
      def main() :
          pass
      
      posted in Cinema 4D SDK
      ManuelM
      Manuel
    • RE: Move just the axis of a polygonal object

      Hello @rui_mac

      you don't really need to deal with hierarchy as long as you are using global space.
      you have to come back to local space with the new matrix, by multiplying the points position with the inverse matrix.
      Be sure to also move the axis of the object as @mp5gosu stated and to ask the object to update itself.

              # Retrieves selected object
              op = doc.GetActiveObject()
              if op is None:
                  raise TypeError("There's no object selected.")
          
              # Retrieves the matrix of the object
              opmg = op.GetMg()
          
              # Retrieves the point in global space
              pointsGlob = [p * opmg for p in op.GetAllPoints()]
          
              # Starts the undo process (the initial scene to be restored after an undo action)
              doc.StartUndo()
          
              # Notifies a change on the obj
              doc.AddUndo(c4d.UNDOTYPE_CHANGE, op)
          
              # Updates the matrix
              opmg.off = c4d.Vector(0)
              # Updates the axis of the object
              op.SetMg(opmg)
          
              # Notifies the object, that internal data changed and it needs to be updated
              op.Message(c4d.MSG_UPDATE)
          
              # Inverses the matrix to come back to local space
              invopmg = ~opmg
          
              # Retrieves points from global to local space
              newPoints = [p * invopmg for p in pointsGlob]
          
              # Updates the points
              doc.AddUndo(c4d.UNDOTYPE_CHANGE, op)
              op.SetAllPoints(newPoints)
          
              # Ends the undo process (the final scene state)
              doc.EndUndo()
          
              # Updates Cinema 4D
              c4d.EventAdd()
          
      

      Cheers
      Manuel

      posted in General Talk
      ManuelM
      Manuel
    • RE: Retrieve the World Rotation of a Selected Edge?

      hello,

      To answer this question if someone want to do it by code.

      import c4d
      from c4d import gui
      
      # Welcome to the world of Python
      
      
      # Main function
      def main():
          #get the selected object
          op = doc.GetActiveObject()
          if not op:
              raise TypeError("there's no object selected")
          if not op.IsInstanceOf(c4d.Opolygon):
              raise TypeError("obj should be a polygon object")
      
          #setup neighbor
          nbr = c4d.utils.Neighbor()
          nbr.Init(op)
      
          #get the selected edge
          bs = op.GetSelectedEdges(nbr,c4d.EDGESELECTIONTYPE_SELECTION)
          
      
          #maximum number of edge should be number of polygon * 4
          sel = bs.GetAll(op.GetPolygonCount() * 4)
          
          #get all polygon
          vadr = op.GetAllPolygons()
          #init point a and b index to -1 for error check
          a = b = -1
          #initialise a counter to check in sel array.
          countEdge = 0
          
          #we can check now every edge to find the corresponding points. So for each polygon
          for i in xrange(op.GetPolygonCount()):
              #get the polygon information
              pli = nbr.GetPolyInfo(i)
      
              for side in xrange(4): # Test all 4 sides of a polygon
                  # Only proceed if edge is marked
                  # and edge really exists (for triangles side 2 from c..d does not exist as c==d)
      
                  if pli["mark"][side] or (side==2 and vadr[i].c == vadr[i].d): 
                      continue 
                  
                  #if the edge is marked as selected in our sel array
                  if sel[countEdge]:
                      if side==0:
                          a=vadr[i].a; b=vadr[i].b
                      elif side==1:
                          a=vadr[i].b; b=vadr[i].c
                      elif side==2:
                          a=vadr[i].c; b=vadr[i].d
                      elif side==3:
                          a=vadr[i].d; b=vadr[i].a
                  countEdge +=1
      
          if a == -1 or b == -1:
              raise ValueError("points index can't be negatives")
          
          #get all points array 
          points = op.GetAllPoints()
      
          
          #Get the direction of the edge in global coordinate (multiply the points'vector by the matrix of the object)
          opmg = op.GetMg()
          #i've picked a direction from point a to point b so the angle could need to be reverted.
          dirVector = points[b]*opmg - points[a]*opmg
          #normalize a vector is often a good idea.
          dirVector.Normalize()
          
          #transform the vector to hpb note the result is in radian
          hpb = c4d.utils.VectorToHPB(dirVector)
          
          #transform radian to degree
          hpb.x = c4d.utils.RadToDeg(hpb.x)    
          hpb.y = c4d.utils.RadToDeg(hpb.y)
          hpb.z = c4d.utils.RadToDeg(hpb.z)
      
          print "the edge is poiting to",dirVector,"the corresponding angle is", hpb
      
      
      # Execute main()
      if __name__=='__main__':
          main()
      

      Cheers
      Manuel

      posted in Cinema 4D SDK
      ManuelM
      Manuel
    • RE: Modifying or Adding Script Directory?

      Hello,

      On windows, you can create an environment variable named C4D_SCRIPTS_DIR more informations in the doc
      The doc say : Multiple paths can be separated using a semicolon (;) but it's seem buggy at the moment, i have to investigate a bit further and i'll be back if i found something.

      On OSX you don't have environment variables but you can create a Symbolic Link (not an alias). You have to use the Terminal to do so (ln -s /<source>/myscript.py /<preferences path>/library/scripts) This will create a link in your scripts directory.
      I've tried to link from directory to directory but it doesn't work.

      Cheers
      Manuel

      posted in Cinema 4D SDK
      ManuelM
      Manuel
    • RE: LoadFile(Filename file) does not work for layouts.

      Several things here,

      your path on windows should use backslash (\) and not slash (/)
      and you should have two.

      "C:\\Program Files\\MAXON\\Cinema 4D R20 Education\\library\\layout\\myLayout.l4d"
      

      A better way to do this is to use operator so your path will be compatible with all OS.

      // Create a filename to point to "Application directory/library/layout/Animate.l4d"
      const Filename file = GeGetStartupPath() + Filename("library") + Filename("layout") + Filename("Animate.l4d");
      // load the layout.
      Bool result = LoadFile(file);
      // check the result.
      if (!result)
      	DiagnosticOutput("can't load the layout");
      		
      

      I've tested this code in PluginMessage() using C4DPL_COMMANDLINEARGS and C4DPL_PROGRAM_STARTED

      Cheers
      Manuel

      posted in Cinema 4D SDK
      ManuelM
      Manuel
    • RE: Set Use As Render View

      hello,

      For your next threads, please help us keeping things organised and clean.

      • Q&A New Functionality.
      • How to Post Questions especially the tagging part.

      I've added the tags and marked this thread as a question so when you considered it as solved, please change the state 🙂

      There's no function to set the viewport used for rendering.

      Why can't you use CallCommand ? (even if in this case the command is the same what ever the active viewport is)

      you can change it in the BaseContainer of the document. The symbol is not exposed so you have to use the ID itself.

          #DOCUMENT_RVIEW 10001
          bc = doc.GetDataInstance()
          print bc[10001]
          bc[10001] = 1
      

      Cheers,
      Manuel

      posted in Cinema 4D SDK
      ManuelM
      Manuel
    • Building R23 SDK with Visual Studio 16.9

      Hi,

      There's a know issue about compiling the R23 sdk with Visual Studio 16.9.
      Trying to compile with this version will lead you to the following error:

      Error C2079 'maxon::ResultBase<RESULT_TYPE>::_value' uses undefined class 'maxon::BlendAnimationRef' math.framework <path to the file> 571
      

      This issue will be fixed in the next update.
      To fix the issue, open the file objectbase.h of core.framework and change those lines :

      around line 1088:

      // template <typename I, typename = I*> struct VirtualCreate
      // become 
      template <typename I, typename = I*(*)()> struct VirtualCreate
      
      

      around line 1102:

      // template <typename I> struct VirtualCreate<I, decltype(I::Create())>
      // become 
      template <typename I> struct VirtualCreate<I, decltype(&I::Create)>
      

      Cheers,
      Manuel

      posted in News & Information news sdk
      ManuelM
      Manuel
    • RE: Use a list or array for data?

      Hello,

      The first thing that come to mind is a for loop. But you need to get informations in userData that are stored in your obj in a chaotic way.

      What i suggest here, is to create those userdata in a more organized way (maybe with a script) so can easily create a loop in that case.

      in pseudo code it could be something like this at the end.

      tracks = obj.GetCTracks()
      
      for index, track in enumerate(tracks):
      	obj[c4d.ID_USERDATA, index * 4  + 0] = track.GetName()
      	track[c4d.CID_SOUND_ONOFF] = obj[index*4 + 1]
              track[c4d.CID_SOUND_START] = obj[index*4 + 2]
              track[c4d.CID_SOUND_NAME]  = obj[index*4 + 3]
      
      

      Let me know what do you think about it ?

      Cheers

      posted in General Talk
      ManuelM
      Manuel
    • RE: Buttons ids

      Hello,

      there's no real list where you can find easily those IDs.

      @m_adam came with this idea :
      open the Script log (menu script > script log) and click the button you want to call.
      in the console you will see something like

       c4d.CallButton(tool(), c4d.MDATA_NEWTRANSFORM)
      

      so you know that the ID is c4d.MDATA_NEWTRANSFORM

      Another place to look for, is the folder ressource/modules where you can find the files .res that build the ui. (using your OS search function will help a lot) Openning those file you will find the information

      BUTTON MDATA_NEWTRANSFORM	{ FIT_H; }
      

      and you can maybe open the file with the same name with the extention .h where you will find the id

      MDATA_NEWTRANSFORM							= 701,   // Button
      

      Other than that, there are the enums on the documentation like this page but it's not really "user friendly"

      Let me know.

      Cheers
      Manuel

      posted in Cinema 4D SDK
      ManuelM
      Manuel
    • RE: is there a market to sell my plugin?

      Hello,

      There's no official market for Cinema 4D's plugins own by Maxon.

      But, as an old plugins seller myself, i can try to give you some hints and share my experience.

      If you want to sell your plugins, you have several options and some legal part to consider.

      You must collect VAT for example. As a European, things can be different depending on witch country your client is from. I'm far to be a specialist, and there is some tools that can handle those things for you. Don't be afraid of legal part.

      1 - You can run your own shop by having your own provider, using "simple tools" like
      Wordpress + WooCommerce. If you add plugins to handle VAT and other legal stuff, this can be a breeze. You have your own Design, you can handle serial numbers the way you want, you have total control.

      2 - Using a market place ( Aescripts or Gumroad for exemple) have some advantages :

      • they will manage the legal stuff for you,
      • they help you if you got any question
      • you can focus on your development
      • they will generate a lot more traffic and your tools will be more visible (people can come for another plugins and check what they have in the catalog)

      You also have to consider the tax they will charge of course. They will varie form plateform to plateform. Don't hesitate to contact them.

      Final point, DO IT, it's fun, you will have some nice feedback, having people, sending work they have done with your tools, from all over the world is just a blast.

      Cheers
      Manuel

      posted in General Talk
      ManuelM
      Manuel
    • RE: Make Objects Editable (Streamlined)?

      hello,

      seem that you forgot brackets on your list of object.

      list = [op]
      

      watch out where you are running those command, some operation have to be executed in the main thread
      on a script it should look like this

      import c4d
      from c4d import gui
      from c4d import utils
      # Welcome to the world of Python
      
      
      # Script state in the menu or the command palette
      # Return True or c4d.CMD_ENABLED to enable, False or 0 to disable
      # Alternatively return c4d.CMD_ENABLED|c4d.CMD_VALUE to enable and check/mark
      #def state():
      #    return True
      
      # Main function
      def main():
      
          op = doc.GetActiveObject()
      
          if not op: 
              raise TypeError("there's no object selected")
          res = utils.SendModelingCommand(
                                    command = c4d.MCOMMAND_MAKEEDITABLE,
                                    list = [op])
          #check if res is a list 
          if res is False:
              raise TypeError("make editable didn't worked")
          elif res is True:
              print "make editable should not return true"
          elif isinstance(res, list):
              doc.InsertObject(res[0])
          
          
          c4d.EventAdd()
      
      # Execute main()
      if __name__=='__main__':
          main()
      

      Cheers
      Manuel

      posted in Cinema 4D SDK
      ManuelM
      Manuel
    • RE: Modifying or Adding Script Directory?

      thanks a lot and happy to learn your issue is solved.

      About the multi-path separated with semicolon, I can confirm it is bugged at the moment but will be fixed in the next release.

      Cheers
      Manuel

      posted in Cinema 4D SDK
      ManuelM
      Manuel
    • RE: Retrieve tags that have only "logical" Priority Data?

      hello,

      you can check in the description if the parameter does have a name

          descTag = tag.GetDescription(c4d.DESCFLAGS_DESC_NONE)
          bc = descTag.GetParameter(c4d.EXPRESSION_PRIORITY)
          if bc[c4d.DESC_NAME]  is not None:
              print tag
          
      

      If the tag doesn't have any priority list, the basecontainer will be empty but not "None"

      Cheers
      Manuel

      posted in Cinema 4D SDK
      ManuelM
      Manuel
    • RE: DrawLine Obscured by objects

      Hello,

      You have to use the Draw function from the tooldata

      You can store the points in an array and than use that array in your draw function.

      try to check if the mouse have move "enough" instead of 0 to avoid creating too much points in the array.

        if dx < 1.0 and dy < 1.0:
                      result, dx, dy, channel = win.MouseDrag()
                      continue
      

      and once you have your array of points you can juste iterate it and draw lines

          def Draw(self, doc, data, bd, bh, bt, flags):
      
              if not flags:
                  # Sets the pen color
                  bd.SetPen(c4d.Vector(0, 0, 1.0))
                  # Iterates through the array getting columns 1_2, 2_3, ... n-1_n
                  for pa,pb in zip(self.data,self.data[1:]):
                      # Draw a line point by point
                      bd.DrawLine2D(pa, pb)
      
              return c4d.TOOLDRAW_NONE
        
      

      Cheers
      Manuel

      posted in Cinema 4D SDK
      ManuelM
      Manuel
    • RE: how to write description "Coordinates"

      hello,

      thanks @mp5gosu for the answer.

      To add something here, you can have a look at the file r_info_manager.res, that's the coordinate manager. This have to be reproduce the same way in a CreateLayout.

      On a description, things are a bit easier you can use a customGUI CUSTOMGUI_SUBDESCRIPTION
      on the .res file juste add this line

      VECTOR ID_BASEOBJECT_REL_POSITION { UNIT METER;  CUSTOMGUI SUBDESCRIPTION; }
      

      on the GetDDescription you juste add a vector and specify the customGUI to use

      		DescID cid = DescLevel(vectorID, DTYPE_VECTOR, 0);
      			// check if this parameter ID is requested (NOTE: this check is important for speed)
      			if (!singleid || cid.IsPartOf(*singleid, nullptr))
      			{
      				// define the new parameter description
      				BaseContainer settings = GetCustomDataTypeDefault(DTYPE_VECTOR);
      				settings.SetString(DESC_NAME, "A vector"_s);
      				settings.SetInt32(DESC_CUSTOMGUI, CUSTOMGUI_SUBDESCRIPTION);
      			
      				// set the new parameter
      				if (!description->SetParameter(cid, settings, ID_OBJECTPROPERTIES))
      					return false;
      			}
      

      Of course, you can add vector in userData the same way.

      
          bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_VECTOR)
          bc.SetString(c4d.DESC_NAME, "Parameter Name")    # displayed in the Xpresso "Object" node
          bc.SetString(c4d.DESC_SHORT_NAME, "Short Name")  # displayed in the Attribute Manager
          bc.SetInt32(c4d.DESC_CUSTOMGUI, c4d.CUSTOMGUI_SUBDESCRIPTION)
      
      
          op.AddUserData(bc)
      

      Cheers
      Manuel

      posted in Cinema 4D SDK
      ManuelM
      Manuel
    • RE: Change LONG_CYCLE selected Item.

      hello,

      You can set the default value you want in your Init function.
      In any case, the Cycle will show the value corresponding to the value of the parameter.

      #define MY_CYCLE_ID 1000
      
      class DefaultCycleValue : public ObjectData
      {
      	INSTANCEOF(DefaultCycleValue, ObjectData)
      public:
      
      	virtual Bool Init(GeListNode *node)
      	{
      		// set the parameter the value we want, the Custom GUI will show the corresponding line in the box.
      		node->SetParameter(DescID(MY_CYCLE_ID), GeData(1), DESCFLAGS_SET::NONE);
      		return true;
      	};
      
      	virtual Bool GetDDescription(GeListNode* node, Description* description, DESCFLAGS_DESC& flags) override
      	{
      	
      		if (!description->LoadDescription(node->GetType()))
      			return false;
      
      		const DescID* singleid = description->GetSingleDescID();
      		const DescID cid = DescLevel(MY_CYCLE_ID, DTYPE_LONG, 0);
      
      		if (!singleid || cid.IsPartOf(*singleid, nullptr))
      		{
      
      			// add the parameter as long
      			BaseContainer bc = GetCustomDataTypeDefault(DTYPE_LONG);
      
      			// set the custom GUI to a cycle.
      			bc.SetInt32(DESC_CUSTOMGUI, CUSTOMGUI_CYCLE);
      			bc.SetString(DESC_NAME, "Long Cycle"_s);
      			bc.SetInt32(DESC_SCALEH, 1);
      
      			// cycle elements
      			BaseContainer items;
      			items.SetString(0, String("Item 0"));
      			items.SetString(1, String("Item 1"));
      			items.SetString(2, String("Item 2"));
      
      			bc.SetContainer(DESC_CYCLE, items);
      
      			// icons
      			BaseContainer icons;
      			icons.SetInt32(0, IDM_COPY);
      			icons.SetInt32(1, IDM_CUT);
      			icons.SetInt32(2, IDM_DELETE);
      
      			bc.SetContainer(DESC_CYCLEICONS, icons);
      
      
      			description->SetParameter(cid, bc, ID_OBJECTPROPERTIES);
      		}
      
      		flags |= DESCFLAGS_DESC::LOADED;
      
      		return SUPER::GetDDescription(node, description, flags);
      	};
      
      
      	static NodeData *Alloc() { return NewObjClear(DefaultCycleValue); }
      };
      

      Cheers
      Manuel

      posted in Cinema 4D SDK
      ManuelM
      Manuel
    • RE: LoadFile(Filename file) does not work for layouts.

      hello,

      From where are you trying to switch the layout ?

      I've tested on the script manager and in the command line and it's working just as expected.
      If you want Cinema4D to launch with a particular layout you can also use the -layout argument.

      I'm waiting your feedback to have a chance to reproduce your issue.
      I'm assuming you are using the version R20.059.

      Cheers
      Manuel

      posted in Cinema 4D SDK
      ManuelM
      Manuel
    • RE: User Data button Python Tag r19 - avoid "index out of depth" console feedback

      hello,

      The error message make sense as you are trying to reach a level on the DescID that doesn't exist.

      So to avoid that you can simply check the depth of the DescID using GetDepth() to check if it's superior to 1.

      or

      you can create a DescID that you know your button will have and simply compare equalisty

      import c4d
      
      
      myButtonID = 1
      myByttonDescID = c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA , c4d.DTYPE_SUBCONTAINER ,0), c4d.DescLevel(myButtonID, c4d.DTYPE_BUTTON,0))
      
      
      
      def message(id, data):
      
          if id == c4d.MSG_NOTIFY_EVENT:
              event_data = data['event_data']
      
              
              if event_data['msg_id'] == c4d.MSG_DESCRIPTION_COMMAND:
      
                  desc_id = event_data['msg_data']['id']
                  #first solution check if the descid depth is more than one
                  #if desc_id.GetDepth() > 1:
                  
                  #second solution, we compare the id with the descID we know our button will have.
                  if desc_id == myByttonDescID:
                      #Gets Driver Object Global Matrix and stores its Global Position
                      o = doc.SearchObject("Drv")
                      if o:
                          m = o.GetMg()
                          pos = m.off
      
                      #Update Button Hit
                      if desc_id[1].id == 1:
      
                          op[c4d.ID_USERDATA,1][c4d.ID_USERDATA,2] = pos[0]
                          op[c4d.ID_USERDATA,1][c4d.ID_USERDATA,4] = pos[1]
                          op[c4d.ID_USERDATA,1][c4d.ID_USERDATA,3] = pos[2]
      
                          c4d.EventAdd()
      
                      else:
                          return
      
      
      def main():
          pass
      

      and this will allow you to have several button.

      Cheers
      Manuel

      posted in Cinema 4D SDK
      ManuelM
      Manuel
    • RE: BaseContainer equality ... are you kidding me

      hello,

      We can confirm this. Sorting doesn't help because Sort() sort the IDs removing the value except String and SubContainer.

      As you may know BaseContainer are heavily used in Cinema4d so i doubt that the Operator will be changed but wait and see.
      Once the decision is made, we will update the documentation accordingly.
      Thanks to point us to this, i'll get back with any information that i'll receive.

      So if you want to compare BaseContainer you have to come with your own solution to "really" compare them.

      Cheers
      Manuel

      posted in Cinema 4D SDK
      ManuelM
      Manuel
    • RE: Get Deformed Points from Skin Deformer

      hello,

      The points are stored in a local space (local coordinates). To get the global space (world coordinates) you have to multiply by the object's matrix.

      there's an example in c++

      To go from global to local, just multiply by the invert matrix

          #Retrieves the active Object
          node = doc.GetActiveObject()
          
          if node == None:
              raise ValueError ("there's no object selected")
          
          #Retrieves the deformed cache
          cache = node.GetDeformCache()
          if cache == None:
              raise ValueError("no cache found")
          
          #Retrieves All points    
          points = cache.GetAllPoints()
          
          #Retrieves the object Matrix to go from Object to world coordinates(local to global)
          opMg = node.GetMg()
          # Prints out points in local coordinates.
          print points
          
          # Transforms points to global coordinates
          points = [p * opMg for p in points]
          
          # Prints points in global coordinates.
          print points
          
          # Global to local
          # Retrieves the inverse Matrix of the object with ~
          invOpMg = ~opMg
          # Multiplies each point by the inverse matrix
          points = [p * invOpMg for p in points]
      
          print points
        
      

      Cheers
      Manuel

      posted in Cinema 4D SDK
      ManuelM
      Manuel