Hey @chuanzhen,
Thank you for reporting this. Yes, you are misunderstanding this function a bit. But I have to admit that I did too first, because the function is very weird in what it does. But when you look at the C++ docs, there is a warning.

I have just updated the Python docs for the next release to also contain the warning. So, this function is effectively only useable for containers holding strings. As you can see by my example below, it does not only remove all values but turns them into strings (and then leaves them empty when the source was not a string). The function is badly named.
Find also a little example below to do what you want: sort a container by values.
Cheers,
Ferdinand
Result
Root (None , id = -1):
├── 4 (DTYPE_LONG): 4
├── 5 (DTYPE_LONG): 5
├── 6 (DTYPE_LONG): 6
├── 7 (DTYPE_LONG): 7
├── 8 (DTYPE_LONG): 8
├── 9 (DTYPE_LONG): 9
├── 10 (DTYPE_LONG): 10
├── 11 (DTYPE_LONG): 11
├── 0 (DTYPE_LONG): 0
├── 1 (DTYPE_LONG): 1
└── 2 (DTYPE_LONG): 2
--------
Root (None , id = -1):
├── 1 (DTYPE_STRING):
├── 4 (DTYPE_STRING):
├── 5 (DTYPE_STRING):
├── 6 (DTYPE_STRING):
├── 7 (DTYPE_STRING):
├── 8 (DTYPE_STRING):
├── 9 (DTYPE_STRING):
├── 10 (DTYPE_STRING):
├── 11 (DTYPE_STRING):
├── 0 (DTYPE_STRING):
└── 2 (DTYPE_STRING):
Code
import c4d
import mxutils
doc: c4d.documents.BaseDocument # The currently active document.
op: c4d.BaseObject | None # The primary selected object in `doc`. Can be `None`.
def main() -> None:
bc = c4d.BaseContainer()
for i in range(4,12):
bc.SetInt32(i,i)
for i in range(3):
bc.SetInt32(i,i)
print(mxutils.GetContainerTreeString(bc))
print("--------")
bc.Sort()
print(mxutils.GetContainerTreeString(bc))
if __name__ == '__main__':
main()
Custom Sorting Function
Result
Root (None , id = -1):
├── 0 (DTYPE_LONG): 0
├── 1 (DTYPE_LONG): 1
├── 2 (DTYPE_LONG): 2
├── 3 (DTYPE_LONG): 3
├── 5 (DTYPE_LONG): 5
├── 6 (DTYPE_LONG): 6
├── 7 (DTYPE_LONG): 7
├── 8 (DTYPE_LONG): 8
├── 9 (DTYPE_LONG): 9
├── 10 (DTYPE_LONG): 10
├── 11 (DTYPE_LONG): 11
├── 12 (DTYPE_LONG): 12
├── 13 (DTYPE_LONG): 13
├── 14 (DTYPE_LONG): 14
├── 15 (DTYPE_LONG): 15
├── 16 (DTYPE_LONG): 16
├── 17 (DTYPE_LONG): 17
├── 18 (DTYPE_LONG): 18
├── 19 (DTYPE_LONG): 19
└── 4 (DTYPE_SUBCONTAINER , id = -1):
├── 0 (DTYPE_LONG): 0
├── 1 (DTYPE_LONG): 1
├── 2 (DTYPE_LONG): 2
├── 3 (DTYPE_LONG): 3
├── 4 (DTYPE_LONG): 4
├── 5 (DTYPE_LONG): 5
├── 6 (DTYPE_LONG): 6
├── 7 (DTYPE_LONG): 7
├── 8 (DTYPE_LONG): 8
├── 9 (DTYPE_LONG): 9
├── 10 (DTYPE_LONG): 10
├── 11 (DTYPE_LONG): 11
├── 12 (DTYPE_LONG): 12
├── 13 (DTYPE_LONG): 13
├── 14 (DTYPE_LONG): 14
├── 15 (DTYPE_LONG): 15
├── 16 (DTYPE_LONG): 16
├── 17 (DTYPE_LONG): 17
├── 18 (DTYPE_LONG): 18
└── 19 (DTYPE_LONG): 19
Code
import c4d
import mxutils
import random
doc: c4d.documents.BaseDocument # The currently active document.
op: c4d.BaseObject | None # The primary selected object in `doc`. Can be `None`.
def SortContainer(bc: c4d.BaseContainer, mode: str = "value") -> None:
"""Sorts the given container by ID or value.
This function reallocates sub-containers but modifies the passed in container in place. It would also be
possible to modify the sub-container in place, but more work to implement and also more complex to run.
Due to the fact that we have to copy container data, this is also not the cheapest function.
"""
def copy(bc: c4d.BaseContainer) -> list[tuple[int, any]]:
"""Copies the contents of the given BaseContainer to a list of (ID, value) tuples.
"""
result = []
for i, v in bc:
if isinstance(v, c4d.BaseContainer):
v = copy(v)
result.append((i, v))
return result
def build(bc: c4d.BaseContainer, items: list[tuple[int, any]]) -> None:
"""Builds a BaseContainer from the given list of (ID, value) tuples.
"""
bc.FlushAll()
for i, v in items:
if isinstance(v, list):
sub = c4d.BaseContainer()
build(sub, v)
v = sub
bc[i] = v
def sort_items(items: list[tuple[int, any]], mode: str) -> list[tuple[int, any]]:
"""Sorts the given list of (ID, value) tuples by ID or value. Nested containers are sorted recursively.
"""
def is_nested(item: tuple[int, any]) -> bool:
return isinstance(item[1], (list, tuple))
def sort_key(item: tuple[int, any]) -> tuple[bool, any]:
item_id, value = item
# The first key puts nested values after scalar values.
# The second key is only compared within the same group.
if is_nested(item):
return True, item_id if mode == "id" else 0
return False, item_id if mode == "id" else value
# Sort nested contents recursively before sorting this level.
for index, (item_id, value) in enumerate(items):
if is_nested((item_id, value)):
items[index] = (item_id, sort_items(value, mode))
return sorted(items, key=sort_key)
items: list[tuple[int, any]] = copy(bc)
return build(bc, sort_items(items, mode))
def main() -> None:
"""
"""
bc: c4d.BaseContainer = c4d.BaseContainer()
data: list[int] = list(range(20))
random.shuffle(data)
for v in data:
bc[v] = v
copy: c4d.BaseContainer = bc.GetClone(0)
bc[4] = copy
print(mxutils.GetContainerTreeString(bc))
print("--------")
SortContainer(bc)
print(mxutils.GetContainerTreeString(bc))
if __name__ == '__main__':
main()