Cinema 4D - my Python scripts - alphabetically sort objects

From NoskeWiki
Jump to navigation Jump to search

About

NOTE: This page is a daughter page of: Cinema 4D


This page contains code for a Python script I've written for Cinema 4D. To understand how it compiles see Cinema 4D - my Python scripts.

alphabetically_sort_objects.py

alphabetically_sort_objects.py

# Script for alphabetically sorting objects under any parent
# object(s) you select. Note that if you wish to sort the
# top-most objects you should put them under a null object first.

import c4d
from c4d import gui

# Unique id numbers for each of the GUI elements:
LBL_USAGE = 1000
RADIO_GROUP = 10000
RADIO_ALPABETIC = 10001
RADIO_REVERSE_ALPHABETIC = 10002
CHK_FACTOR_NUMBERS = 20003
GROUP_OPTIONS = 30000
BTN_OK = 30001
BTN_CANCEL = 30002

class OptionsDialog(gui.GeDialog):
  """ Dialog for reordering objects alphabetically.
  """
  def CreateLayout(self):
    self.SetTitle('Object Sorter (Albabetical Sorting)')
    self.AddMultiLineEditText(LBL_USAGE, c4d.BFV_SCALEFIT, inith=40, initw=500,
                              style=c4d.DR_MULTILINE_READONLY)
    self.SetString(LBL_USAGE, 'USAGE: For all parent objects selected, sorts\n ' +
                              '    all their immediate children alphabetically') 
    # Radio Button Group - rename parent or children:
    self.AddRadioGroup(RADIO_GROUP, c4d.BFH_LEFT, 1, 1)
    self.AddChild(RADIO_GROUP, RADIO_ALPABETIC, 'A-Z')
    self.AddChild(RADIO_GROUP, RADIO_REVERSE_ALPHABETIC, 'Z-A')
    self.SetBool(RADIO_ALPABETIC, True)  # Set first radio button on.
    self.GroupEnd()
    self.AddSeparatorH(c4d.BFH_SCALE);
    # Checkbox - factor numbers:
    self.AddCheckbox(CHK_FACTOR_NUMBERS, c4d.BFH_SCALEFIT,
                     initw=1, inith=1, name='consider number values (9 before 10)')
    self.SetBool(CHK_FACTOR_NUMBERS, True)     
    self.AddSeparatorH(c4d.BFH_SCALE);
    # Buttons - an Ok and Cancel button:
    self.GroupBegin(GROUP_OPTIONS, c4d.BFH_CENTER, 2, 1)
    self.AddButton(BTN_OK, c4d.BFH_SCALE, name='OK')
    self.AddButton(BTN_CANCEL, c4d.BFH_SCALE, name='Cancel')
    self.GroupEnd()
    self.ok = False
    return True
  
  # React to user's input:
  def Command(self, id, msg):
    if id==BTN_CANCEL:
      self.Close()
    elif id==BTN_OK:
      self.ok = True
      self.option_reverse_order = self.GetBool(RADIO_REVERSE_ALPHABETIC)
      self.option_pad_numbers = self.GetBool(CHK_FACTOR_NUMBERS)
      self.Close()
    return True


def pad_whole_numbers(haystack, pad_digits):
  """Inputs a string and zero pabds any contigous numbers.
  
  Example:
    pad_whole_numbers('Bld 99, Cube 1-88', 4) -> 'Bld 0099, Cube 0001b-0088'
    pad_whole_numbers('Bld 100, Cube 90a', 4) -> 'Bld 0100, Cube 0090a'
  This can be useful for sorting when you want Bld 99 to be before Bld 1000.
  
  Args:
    haystack: String to alter.
    pad_digits: Number of digits to pad each number to.
    
  Returns:
    Altered string with padded numbers.
  """
  return_str = ''
  conseq_digits = 0
  # Iterate from last char to first, plus 1 extra:
  for i in range(len(haystack)-1,-2,-1):
    if (i>=0 and haystack[i] >= '0' and haystack[i] <= '9'):
      conseq_digits += 1
    elif (conseq_digits > 0):
      if (pad_digits > conseq_digits):
        return_str += '0' * (pad_digits - conseq_digits)
      conseq_digits = 0
    if (i>=0):
      return_str += haystack[i]
  
  if len(return_str) == len(haystack):
    return haystack
  return return_str[::-1]  # Reverse the string.


def main():
  # Get the selected objects, without children.
  selection = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_SELECTIONORDER)
  
  if len(selection) <= 0:
    gui.MessageDialog('Please select the parents of objects you wish to sort')
    return
  
  # Open the options dialogue to let users choose their options.
  dlg = OptionsDialog()
  dlg.Open(c4d.DLG_TYPE_MODAL, defaultw=300, defaulth=50)
  if not dlg.ok:
    return
    
  doc.StartUndo()  # Start undo block.
  for i in range(0,len(selection)):
    parent = selection[i]
    obj = selection[i].GetDown()
    
    olist = []  # List of [name, objects] pairs.
    while obj:  # Add all the child objects to the list:
      name = obj.GetName()
      if (dlg.option_pad_numbers):
        name = pad_whole_numbers(name, 10)
        print(obj.GetName() + ' > ' + name)
      olist.append([name,obj])  # Name then object.
      obj = obj.GetNext()

    olist.sort()       # Sort the list by the name.
    if not dlg.option_reverse_order:
      olist.reverse()  # Reverse list (since we insert backwards)
    
    for i,o in enumerate(olist):            # For each object in sorted list:
      doc.AddUndo(c4d.UNDOTYPE_CHANGE,o[1])
      o[1].Remove()                           # Remove the object.
      doc.InsertObject(o[1],parent=parent)    # Now insert it under the parent.
  doc.EndUndo()   # End undo block.
  c4d.EventAdd()  # Update C4D to see changes.

if __name__=='__main__':
  main()


See Also


Code license
For all of the code on my site... if there are specific instruction or licence comments please leave them in. If you copy my code with minimum modifications to another webpage, or into any code other people will see I would love an acknowledgment to my site.... otherwise, the license for this code is more-or-less WTFPL (do what you want)! If only copying <20 lines, then don't bother. That said - if you'd like to add a web-link to my site www.andrewnoske.com or (better yet) the specific page with code, that's a really sweet gestures! Links to the page may be useful to yourself or your users and helps increase traffic to my site. Hope my code is useful! :)