<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[C4D uses Python to search for referenced objects]]></title><description><![CDATA[<p dir="auto">This is my Python code. I want to write a button to delete invalid null objects, even if they are at any level. However, this code is invalid in check_deference because executing the code will delete the linked null objects together. How can I find the referenced objects?</p>
<p dir="auto">In addition, I have attached an image where I want to remove the red null object and keep the green null object (green object 1111 is linked to the target label, hoping to handle different levels), as it points to the target label. Other default colors are used for various working conditions and have already met the code settings..</p>
<p dir="auto">Thank you.</p>
<p dir="auto"><img src="/forum/assets/uploads/files/1739387125403-snipaste_2025-02-13_03-01-41.png" alt="Snipaste_2025-02-13_03-01-41.png" class=" img-fluid img-markdown" /></p>
<pre><code>def check_references(obj):
    references = []
    
    # 检查标签引用
    for tag in obj.GetTags():
        tag_type = tag.GetType()
        if tag_type in [c4d.Tphong, c4d.Ttexture, c4d.Texpresso]:  # 更新为 Texpresso（表达式标签）
            references.append(f"Tag link: {tag_type}")
    
    if references:
        print(f"Object {obj.GetName()} has references: {', '.join(references)}")
    return references

# 判断 Null 对象是否为空
def is_empty_null(obj):
    if not obj or obj.GetType() != c4d.Onull:
        return False

    if obj.GetTags():
        return False

    child = obj.GetDown()
    while child:
        if child.GetType() != c4d.Onull:
            return False
        if not is_empty_null(child):
            return False
        child = child.GetNext()

    return True

# 获取场景中所有对象
def get_all_objects(obj):
    all_objects = []
    while obj:
        all_objects.append(obj)
        all_objects.extend(get_all_objects(obj.GetDown()))
        obj = obj.GetNext()
    return all_objects

# 删除无效 Null 对象
def delete_null_objects():
    doc = c4d.documents.GetActiveDocument()
    if not doc:
        return

    doc.StartUndo()

    # 获取场景中的所有对象
    objects = doc.GetObjects()

    for obj in objects:
        if obj.GetType() == c4d.Onull:

            if check_references(obj):
                continue  # 被引用的对象跳过

            # 只有 `is_empty_null` 返回 True 才会删除
            if not is_empty_null(obj):
                continue

            # 记录删除操作的撤销
            doc.AddUndo(c4d.UNDOTYPE_DELETE, obj)

            # 获取 Null 对象的父对象
            parent = obj.GetUp()

            # 获取子对象并移到父对象下
            child = obj.GetDown()
            while child:
                next_child = child.GetNext()
                child.Remove()

                # 记录撤销（对于每个子对象的改变）
                if parent:
                    doc.AddUndo(c4d.UNDOTYPE_CHANGE, child)
                    parent.InsertUnder(child)

                child = next_child

            # 删除 Null 对象
            obj.Remove()

    # 更新对象列表，因为在删除过程中可能会修改它
    objects = doc.GetObjects()

    # 额外逻辑：删除非 `Null` 对象下的空 `Null` 子对象
    for obj in objects:
        child = obj.GetDown()
        while child:
            next_child = child.GetNext()

            # 如果子对象是 `Null` 且为空，并且没有标签，则递归删除该 `Null` 对象及其子对象
            if child.GetType() == c4d.Onull:
                if is_empty_null(child):
                    doc.AddUndo(c4d.UNDOTYPE_DELETE, child)
                    child.Remove()
                else:
                    # 如果子对象不是空 `Null`，递归检查其子级
                    delete_empty_null_children(child)

            child = next_child

    # 更新界面
    c4d.EventAdd()

    doc.EndUndo()

# 递归删除子级空 Null 对象
def delete_empty_null_children(parent):
    doc = c4d.documents.GetActiveDocument()
    child = parent.GetDown()
    while child:
        next_child = child.GetNext()
        if child.GetType() == c4d.Onull:
            if is_empty_null(child):
                # 记录撤销
                doc.AddUndo(c4d.UNDOTYPE_DELETE, child)
                child.Remove()
            else:
                delete_empty_null_children(child)

        child = next_child
</code></pre>
]]></description><link>http://developers.maxon.net/forum/topic/16000/c4d-uses-python-to-search-for-referenced-objects</link><generator>RSS for Node</generator><lastBuildDate>Mon, 20 Apr 2026 21:20:36 GMT</lastBuildDate><atom:link href="http://developers.maxon.net/forum/topic/16000.rss" rel="self" type="application/rss+xml"/><pubDate>Wed, 12 Feb 2025 19:12:37 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to C4D uses Python to search for referenced objects on Thu, 13 Feb 2025 12:32:01 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/forum/user/ferdinand">@<bdi>ferdinand</bdi></a> Thank you so much. He perfectly solved all my problems</p>
]]></description><link>http://developers.maxon.net/forum/post/75797</link><guid isPermaLink="true">http://developers.maxon.net/forum/post/75797</guid><dc:creator><![CDATA[Neekoe]]></dc:creator><pubDate>Thu, 13 Feb 2025 12:32:01 GMT</pubDate></item><item><title><![CDATA[Reply to C4D uses Python to search for referenced objects on Thu, 13 Feb 2025 11:56:05 GMT]]></title><description><![CDATA[<p dir="auto">Hello <a class="plugin-mentions-user plugin-mentions-a" href="/forum/user/neekoe">@<bdi>Neekoe</bdi></a>,</p>
<p dir="auto">Thank you for reaching out to us. We were not 100% sure in our morning meeting if we understood your question correctly. But I basically understood it as <em>'how can I find out if a <code>C4DAtom</code> is referenced in a <code>BaseLink</code> in a scene?'</em>. So, to pick an example, you would want to find out if the node <em>A</em> shown in the screen below is linked anywhere in the scene. In that case the answer would be 'yes', since <em>A</em> is linked in a <em>Target Tag</em> of <em>B</em>.</p>
<p dir="auto"><img src="/forum/assets/uploads/files/1739438149695-ce336c36-43eb-4b6e-b7a6-f95380b8d7c8-image.png" alt="ce336c36-43eb-4b6e-b7a6-f95380b8d7c8-image.png" class=" img-fluid img-markdown" /></p>
<p dir="auto">The goal then seems to be to delete such objects which are not referenced in any manner. There are two problems here at play:</p>
<ol>
<li>Abstracted scene traversal, i.e., iterating/recursing over all elements of a scene: The assumption of your code is that a null object is only linked in tags. Which can work when you do not care about other cases of these nulls being referenced. But in practice any form of <code>BaseList2D</code> can hold a link to your null object, i.e., there could be other objects, materials, shaders, etc. linking to that null. To visit each node in a scene you then need such abstracted scene traversal. There are many postings about this subject in the forum, <a href="https://developers.maxon.net/forum/topic/14182/can-i-get-active-things-order-with-python/2" target="_blank" rel="noopener noreferrer nofollow ugc">here</a> is for example one. In 2025.0.0 we added <a href="https://developers.maxon.net/docs/py/2025_0_0/modules/mxutils/index.html#mxutils.RecurseGraph" target="_blank" rel="noopener noreferrer nofollow ugc">mxutils.RecurseGraph</a> which does that out of the box.</li>
<li>Figuring out if a given node holds a parameter of type <code>DTYPE_BASELISTLINK</code> which also happens to point towards your null. You can use <a href="https://developers.maxon.net/docs/py/2025_1_0/modules/c4d/C4DAtom/index.html?highlight=getdescription#C4DAtom.GetDescription" target="_blank" rel="noopener noreferrer nofollow ugc">C4DAtom.GetDescription</a> to look for parameters of a specific type. <a class="plugin-mentions-user plugin-mentions-a" href="/forum/user/i_mazlov">@<bdi>i_mazlov</bdi></a> has once shown <a href="https://developers.maxon.net/forum/topic/15647/get-all-baselistlink-parameters-using-specific-baseobject/2" target="_blank" rel="noopener noreferrer nofollow ugc">here</a> how this can be done.</li>
</ol>
<p dir="auto">There are also some issues with your code, as for example that seems to point into an <a href="https://en.wikipedia.org/wiki/Time_complexity" target="_blank" rel="noopener noreferrer nofollow ugc">exponential time complexity</a> direction, i.e., you seem to plan to iterate over all objects in a scene (to find potential objects to remove). That would be very slow.</p>
<p dir="auto">Find below how I would write something like this. Please note that these scene operation tasks tend to be more finnicky than one usually assumes, so you might have to add features yourself. We cannot debug your code for you or write a solution for you.</p>
<p dir="auto">Cheers,<br />
Ferdinand</p>
<h4>Result</h4>
<p dir="auto">Before:<br />
<img src="/forum/assets/uploads/files/1739447287091-318500b7-f574-4083-9f9a-3dc9f8593a3b-image-resized.png" alt="318500b7-f574-4083-9f9a-3dc9f8593a3b-image.png" class=" img-fluid img-markdown" /><br />
After:<br />
<img src="/forum/assets/uploads/files/1739447421620-06fa4b07-652c-4bd2-887b-8fb22d494d68-image-resized.png" alt="06fa4b07-652c-4bd2-887b-8fb22d494d68-image.png" class=" img-fluid img-markdown" /></p>
<h4>Code</h4>
<pre><code class="language-py">"""Demonstrates how to search for a scene for nodes that do not appear as links in other objects.

Must be run as a script manager script. Will remove all null objects from a scene which do not appear
as linked objects in other tags or objects (and some other criteria, see main() for details).
"""

import c4d
from typing import Iterator

doc: c4d.documents.BaseDocument  # The currently active document.
op: c4d.BaseObject | None  # The primary selected object in `doc`. Can be `None`.


def Iterate(node: c4d.BaseObject, yieldSiblings: bool = True) -&gt; Iterator[c4d.GeListNode]:
    """Traverses a tree of objects and their tags, yielding each element, including the passed #node.

    I implemented here a traversal tailored to your case. Check out scene traversal threads for more
    abstracted traversal or use 2025's mxutils traversal methods.
    """
    def iterate(node: c4d.GeListNode) -&gt; Iterator[c4d.GeListNode]:
        if not node:
            return
        yield node

        # Yield tags when #node is an object.
        if isinstance(node, c4d.BaseObject):
            for t in node.GetTags():
                yield t

        # Traverse hierarchy.
        for child in node.GetChildren():
            yield from iterate(child)

    while node:
        yield from iterate(node)
        node = node.GetNext() if yieldSiblings else None

def GetLinkedNodes(node: c4d.BaseList2D) -&gt; set[c4d.BaseList2D]:
    """Returns the set (i.e., no repetitions) of nodes this #node has links established to.
    """
    result: set = set()
    for _, descid, _ in node.GetDescription(c4d.DESCFLAGS_DESC_NONE):
        # We found a parameter of type baselink, append the value to the result when it is not null.
        # You might want to fine tune things here, to exclude parameter IDs, e.g., ID_LAYER_LINK, or
        # value types, e.g., c4d.BaseLayer, from being here links you want to consider.
        if descid[0].dtype == c4d.DTYPE_BASELISTLINK and node[descid]:
            result.add(node[descid])
    return result


def main() -&gt; None:
    """
    """
    # Get all the nodes that are somewhere referenced in objects or their tags in the scene. Also
    # store all the nodes which contain links. 
    linkSources: set = set()
    linkTargets: set = set()

    for node in Iterate(doc.GetFirstObject()):
        links: set = GetLinkedNodes(node)
        if links:
            linkSources.add(node)
            linkTargets = linkTargets.union(links)
    
    # Now iterate the scene again, looking for nodes matching our removal criteria.
    result: list[c4d.BaseList2D] = []
    for node in Iterate(doc.GetFirstObject()):
        # Continue when the node is not an object of type Onull, has children, or does appear as a
        # linked node somewhere (e.g., a null object which has a target tag).
        if (node.GetType() != c4d.Onull or node.GetChildren() or 
            node in linkTargets or linkSources.intersection(Iterate(node, False))):
            continue
        
        # This is a node we want to remove.
        result.append(node)

    # Remove the nodes we determined to be removed. We would have broken traversal if we just had
    # removed #node in the loop above, as #Iterate is a generator/coroutine. Removing #node above 
    # would make it a disconnected node and #Iterate would not know how to continue. Could also be
    # solved by first exhausting #Iterate, e.g., `data = list(Iterate(...))`and then iterate over 
    # the result (but that is not so nice performance-wise).
    if result:
        print (f"Removing '{len(result)}' nodes: {result}")
        doc.StartUndo()
        for node in result:
            doc.AddUndo(c4d.UNDOTYPE_DELETEOBJ, node)
            node.Remove()
        doc.EndUndo()
        c4d.EventAdd()
        
if __name__ == '__main__':
    main()

</code></pre>
]]></description><link>http://developers.maxon.net/forum/post/75795</link><guid isPermaLink="true">http://developers.maxon.net/forum/post/75795</guid><dc:creator><![CDATA[ferdinand]]></dc:creator><pubDate>Thu, 13 Feb 2025 11:56:05 GMT</pubDate></item></channel></rss>