Hey @mogh,
Thank you for reaching out to us. Your question is a bit like asking 'Why is water wet?'. The answer is there 'because it is' unless one is willing to get technical. The same applies here, some modeling commands require their inputs to be part of a document and their authors simply designed them to be like this. Discussing the technical details is out of scope of support unless you file a code sharing request (and have a good reason for us granting it).
The SendModelingCommand documentation lines out a few of these commands which require a document, including MCOMMAND_MAKEEDITABLE
and other than indicated by yourself, also MCOMMAND_CURRENTSTATETOOBJECT
. What might not be documented so well, is however the quality of that document. Nothing forces you to use the active document or a document which is loaded. You can very well use a dummy document. This is even required when you are in a threaded context where the modification of loaded documents is forbidden.
Find a brief example below.
Cheers,
Ferdinand
Output
SMC - count = 114, points[:3] = [Vector(0, -100, 0), Vector(0, 100, 0), Vector(38.268, -92.388, 0)]
Cache - count = 114, points[:3] = [Vector(0, -100, 0), Vector(0, 100, 0), Vector(38.268, -92.388, 0)]
Code
"""Demonstrates how to use a dummy document in conjunction with SendModelingCommand() in order to
avoid having to modify a loaded document.
Also provides an alternative approach using caches. Must be run as a Script Manager script.
"""
import c4d
def main() -> None:
"""Runs the example.
"""
# Instantiate an object we want to be subjected to modelling commands and a dummy document to
# carry them out in. This prevents us from having to clean up after ourselves in a loaded
# document or makes using SMC possible in the first place in contexts where modifying loaded
# documents is forbidden.
obj: c4d.BaseObject = c4d.BaseObject(c4d.Osphere)
tempDoc: c4d.documents.BaseDocument = c4d.documents.BaseDocument()
if not obj or not tempDoc:
raise MemoryError(f"{obj = }, {tempDoc = }")
# Insert the object and execute the command.
tempDoc.InsertObject(obj)
result: list[c4d.BaseObject] = c4d.utils.SendModelingCommand(
command = c4d.MCOMMAND_MAKEEDITABLE,
list = [obj],
doc=tempDoc)
if not result or not isinstance(result[0], c4d.PointObject):
raise RuntimeError(f"Command failed.")
count: int = result[0].GetPointCount()
points: list[c4d.Vector] = result[0].GetAllPoints()
print (f"SMC - {count = }, {points[:3] = }")
# #tempDoc will be deallocated once the scope of this function is left. We can call Flush() before
# that which is not necessary but probably good form. With it, both #obj and #result[0] will be
# deallocated.
tempDoc.Flush()
# ----------------------------------------------------------------------------------------------
# When it is only primitive generators you want to evaluate, i.e., things which have a flat
# cache, you can also simply execute the passes on the object, which is probably slightly more
# performant than calling SMC (because SMC will also have to do that).
obj: c4d.BaseObject = c4d.BaseObject(c4d.Osphere)
tempDoc: c4d.documents.BaseDocument = c4d.documents.BaseDocument()
if not obj or not tempDoc:
raise MemoryError(f"{obj = }, {tempDoc = }")
# Build the caches.
tempDoc.InsertObject(obj)
tempDoc.ExecutePasses(None, False, False, True, c4d.BUILDFLAGS_NONE)
# Get the generator cache and its deform cache if there is any (will only be there when
# there is a deformer parented to #obj).
cache: c4d.PointObject = obj.GetCache()
cache = cache.GetDeformCache() or cache
if not isinstance(cache, c4d.PointObject):
raise RuntimeError(f"Cache structure of {obj} does not meet expectations.")
count: int = cache.GetPointCount()
points: list[c4d.Vector] = cache.GetAllPoints()
print (f"Cache - {count = }, {points[:3] = }")
tempDoc.Flush()
if __name__ == '__main__':
main()