Skip to content

load_blend_link

BlendLinkLoader

Bases: BlenderLoader

Link assets from a .blend file.

Source code in client/ayon_blender/plugins/load/load_blend_link.py
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
class BlendLinkLoader(plugin.BlenderLoader):
    """Link assets from a .blend file."""

    product_types = {
        "model", "camera", "rig",
        "layout", "animation", "workfile"
    }
    representations = {"blend"}

    label = "Link Blend Hierarchy"
    icon = "code-fork"
    color = "orange"

    def process_asset(
        self,
        context: dict,
        name: str,
        namespace: Optional[str] = None,
        options: Optional[Dict] = None,
    ) -> Optional[List]:
        filepath = self.filepath_from_context(context)

        # Load a single Collection from the .blend file
        # TODO: Disallow loading same collection?
        folder_name = context["folder"]["name"]
        product_name = context["product"]["name"]

        unique_number = plugin.get_unique_number(folder_name, product_name)
        group_name = plugin.prepare_scene_name(
            folder_name, product_name, unique_number
        )
        namespace = namespace or f"{folder_name}_{unique_number}"
        data = {
            "context": context,
            "name": name,
            "namespace": namespace,
            "group_name": group_name,
            "filepath": filepath,
            "options": options
        }
        loaded_collection = self.get_loaded_collection(**data)
        options = options or dict()
        if options.get("addOverride", False):
            local_copy = add_override(loaded_collection)
            if local_copy:
                loaded_collection = local_copy

        container_collection = containerise(
            name=name,
            namespace=namespace,
            nodes=[loaded_collection],
            context=context,
            loader=self.__class__.__name__,
        )

        return container_collection

    def exec_update(self, container: Dict, context: Dict):
        """Update the loaded asset. """
        repre = context["representation"]
        collection = container["node"]
        if collection.children:
            library = self._get_library_from_collection(collection.children[0])
            filepath = self.filepath_from_context(context)
            filename = os.path.basename(filepath)
            if library:
                library.name = filename
                library.filepath = filepath
                library.reload()
        # refresh UI
        bpy.context.view_layer.update()

        # Update container metadata
        metadata_update(
            collection, {"representation": str(repre["id"])}
        )

    def exec_remove(self, container: Dict) -> bool:
        """Remove existing container from the Blender scene."""
        collection = container["node"]
        target_name = f"{container['namespace']}_{container['name']}"
        if collection.children:
            library = self._get_library_from_collection(collection.children[0])
            if library:
                bpy.data.libraries.remove(library)
            else:
                target_collection = find_collection_by_name(target_name)
                if target_collection:
                    for col in target_collection.children:
                        bpy.data.collections.remove(col)
                    for obj in target_collection.objects:
                        bpy.data.objects.remove(obj)

        # Remove the container collection
        bpy.data.collections.remove(collection)

        target_collection = find_collection_by_name(target_name)
        if target_collection:
            bpy.data.collections.remove(target_collection)

        target_object = find_objects_by_name(target_name)
        if target_object:
            bpy.data.objects.remove(target_object)

        return True

    def _get_library_from_collection(
            self, collection: bpy.types.Collection
    ) -> Optional[bpy.types.Library]:
        """Get the library from the collection."""
        for child in collection.children:
            if child.library:
                return child.library
            if child.override_library and child.override_library.reference:
                return child.override_library.reference.library

        for child in collection.objects:
            if child.library:
                return child.library
            if child.override_library and child.override_library.reference:
                return child.override_library.reference.library

        return None

    def get_loaded_collection(self, **kwargs)-> bpy.types.Collection:
        """Get the loaded collection.

        Returns:
            bpy.types.Collection: The loaded collection.
        """

        container_name = get_container_name(
            kwargs.get("name"),
            kwargs.get("namespace"),
            kwargs.get("context"),
            suffix="CON"
        )
        loaded_collection = bpy.data.collections.get(container_name)
        scene_collection = bpy.context.scene.collection
        group_name = kwargs.get("group_name")
        if loaded_collection and group_name in scene_collection.children:

            message = (
                f"Collection {group_name} already loaded, "
                f"instance to {group_name} is created instead of "
                "linking new collection"
            )
            show_message(f"Collection {group_name} already loaded", message)
            instance = bpy.data.objects.new(name=group_name, object_data=None)
            instance.instance_type = 'COLLECTION'
            instance.instance_collection = loaded_collection
            # Link the instance to the active scene
            bpy.context.scene.collection.objects.link(instance)
            return loaded_collection

        return load_collection(
            kwargs.get("filepath"),
            link=True,
            group_name=group_name
        )

exec_remove(container)

Remove existing container from the Blender scene.

Source code in client/ayon_blender/plugins/load/load_blend_link.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
def exec_remove(self, container: Dict) -> bool:
    """Remove existing container from the Blender scene."""
    collection = container["node"]
    target_name = f"{container['namespace']}_{container['name']}"
    if collection.children:
        library = self._get_library_from_collection(collection.children[0])
        if library:
            bpy.data.libraries.remove(library)
        else:
            target_collection = find_collection_by_name(target_name)
            if target_collection:
                for col in target_collection.children:
                    bpy.data.collections.remove(col)
                for obj in target_collection.objects:
                    bpy.data.objects.remove(obj)

    # Remove the container collection
    bpy.data.collections.remove(collection)

    target_collection = find_collection_by_name(target_name)
    if target_collection:
        bpy.data.collections.remove(target_collection)

    target_object = find_objects_by_name(target_name)
    if target_object:
        bpy.data.objects.remove(target_object)

    return True

exec_update(container, context)

Update the loaded asset.

Source code in client/ayon_blender/plugins/load/load_blend_link.py
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
def exec_update(self, container: Dict, context: Dict):
    """Update the loaded asset. """
    repre = context["representation"]
    collection = container["node"]
    if collection.children:
        library = self._get_library_from_collection(collection.children[0])
        filepath = self.filepath_from_context(context)
        filename = os.path.basename(filepath)
        if library:
            library.name = filename
            library.filepath = filepath
            library.reload()
    # refresh UI
    bpy.context.view_layer.update()

    # Update container metadata
    metadata_update(
        collection, {"representation": str(repre["id"])}
    )

get_loaded_collection(**kwargs)

Get the loaded collection.

Returns:

Type Description
Collection

bpy.types.Collection: The loaded collection.

Source code in client/ayon_blender/plugins/load/load_blend_link.py
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
def get_loaded_collection(self, **kwargs)-> bpy.types.Collection:
    """Get the loaded collection.

    Returns:
        bpy.types.Collection: The loaded collection.
    """

    container_name = get_container_name(
        kwargs.get("name"),
        kwargs.get("namespace"),
        kwargs.get("context"),
        suffix="CON"
    )
    loaded_collection = bpy.data.collections.get(container_name)
    scene_collection = bpy.context.scene.collection
    group_name = kwargs.get("group_name")
    if loaded_collection and group_name in scene_collection.children:

        message = (
            f"Collection {group_name} already loaded, "
            f"instance to {group_name} is created instead of "
            "linking new collection"
        )
        show_message(f"Collection {group_name} already loaded", message)
        instance = bpy.data.objects.new(name=group_name, object_data=None)
        instance.instance_type = 'COLLECTION'
        instance.instance_collection = loaded_collection
        # Link the instance to the active scene
        bpy.context.scene.collection.objects.link(instance)
        return loaded_collection

    return load_collection(
        kwargs.get("filepath"),
        link=True,
        group_name=group_name
    )

BlendLinkLoaderFlat

Bases: BlendLinkLoader

Link assets from a .blend file as flat Object.

Source code in client/ayon_blender/plugins/load/load_blend_link.py
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
class BlendLinkLoaderFlat(BlendLinkLoader):
    """Link assets from a .blend file as flat Object."""

    product_types = {"blendScene"}
    label = "Link Blend (Flat)"

    instance_collections = False
    instance_object_data = False

    options = [
        BoolDef(
            "addOverride",
            label="Add Override",
            default=False,
            tooltip="Add a library override for the loaded asset.",
        ),
        BoolDef(
            "instance_collections",
            label="Instances Collection",
            default=False,
            tooltip=("Create instances for collections, "
                     "rather than adding them directly to the scene."),
        ),
        BoolDef(
            "instance_object_data",
            label="Instance Object Data",
            default=False,
            tooltip=("Create instances for object data which "
                     "are not referenced by any objects"),
        )
    ]

    def get_loaded_collection(self, **kwargs) -> bpy.types.Collection:
        filepath = kwargs.get("filepath")
        group_name = kwargs.get("group_name")
        instance_collections = kwargs.get("options", {}).get(
            "instance_collections",
            self.instance_collections
        )
        instance_object_data = kwargs.get("options", {}).get(
            "instance_object_data", self.instance_object_data
        )
        return link_collection(
            filepath=filepath,
            group_name=group_name,
            instance_collections=instance_collections,
            instance_object_data=instance_object_data
        )