1
$\begingroup$

blender file path and contained

How do I append a custom object from an external blender file using relative paths so I can pack the file with the addon? I can append the file easily with absolute path like this but I could fined how to do it with a relative path.

  • external blender file path ---> D:\blend_folder
  • blender file name ---> external_blender_file.blend
  • object name ---> object_2

This is using an absolute path:

# import bpy
# bpy.ops.wm.append(filename="OBJECT_NAME", directory="/PATH/TO/BLENDFILE/NAME.blend\\Object\\")

import bpy
bpy.ops.wm.append(filename="object_2", directory="D:/blend_folder/external_blender_file.blend\\Object\\")

How to make it work with a relative path?

$\endgroup$
  • $\begingroup$ image did'not show $\endgroup$ – Husham Apr 7 at 14:57
3
$\begingroup$

There is a pretty nice example using BlendDataLibraries and a relative path in the API: https://docs.blender.org/api/current/bpy.types.BlendDataLibraries.html

Example script to append all objects named Cube... from a blend file called link_library.blend in the same folder as the script:

import bpy
import os

# "//" prefix is a Blender specific identifier for the current blend file
#filepath = "//link_library.blend"
#abs_filepath = bpy.path.abspath(filepath) # returns the absolute path

# os.path.abspath(__file__) returns path to the script
filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), "link_library.blend")

# link all objects starting with 'Cube'
with bpy.data.libraries.load(filepath, link=False) as (data_from, data_to):
    data_to.objects = [name for name in data_from.objects if name.startswith("Cube")]

# link object to scene collection
for obj in data_to.objects:
    if obj is not None:
       bpy.context.collection.objects.link(obj)

Assuming the folder structure is like:

my_folder/
├── my_script.py
├── link_library.blend/
    ├── Objects
        ├── Cube

Further reading:


Can be wrapped into an operator easily. I suggest declare at least two StringProperties, one to hold the name of the blend and another one for the actual object name, which basically allows to pass arguments when calling the operator or when adding the operator to any layout:

class MY_OT_custom_append(bpy.types.Operator):
    ...
    blend = StringProperty(name="Library Blend File")
    object_name = StringProperty(name="Object Name")
    ...

 # On a panel
 class MY_PT_custom_panel(Panel):
    ...
    row = layout.row()
    props = row.operator(MY_OT_custom_append.bl_idname)
    props.blend = "link_library.blend"
    props.object_name = "Cube"
    ...

...
# Test call
bpy.ops.scene.custom_append(blend="link_library.blend", object_name="Cube")

Read: How to pass multiple operator properties via UI layout?


custom_append.py

Panel and operator example to append given/known objects from another blend file next to the Add-on (see the folder structure above):

enter image description here

bl_info = {
    "name": "Custom Append Add-on",
    "description": "",
    "author": "",
    "version": (0, 1),
    "blender": (2, 80, 0),
    "location": "3D View > Tools",
    "warning": "", # used for warning icon and text in addons panel
    "wiki_url": "",
    "tracker_url": "",
    "category": "Development"
}


import bpy
import os

from bpy.props import StringProperty
from bpy.types import Panel, Operator

# ------------------------------------------------------------------------
#    Append Operator
# ------------------------------------------------------------------------

class MY_OT_custom_append(bpy.types.Operator):
    bl_idname = "scene.custom_append"
    bl_label = "Custom Append Operator"
    bl_options = {'REGISTER'}

    blend = StringProperty(name="Library Blend File")
    object_name = StringProperty(name="Object Name")

    def execute(self, context):   

        # os.path.abspath(__file__) returns path to the addon
        filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), self.blend)

        # -> Test whether the file exist
        if not os.path.isfile(filepath):
            self.report({'WARNING'},"{} does not exist".format(self.blend))
            return {'CANCELLED'}

        # link all objects starting with 'Cube'
        with bpy.data.libraries.load(filepath, link=False) as (data_from, data_to):
            data_to.objects = [name for name in data_from.objects if name == self.object_name]

        # -> Test whether the obejct exist
        if not data_to.objects:
            self.report({'WARNING'},"{} not found in {}".format(self.object_name, self.blend))
            return {'CANCELLED'}

        # link object to scene collection
        for obj in data_to.objects:
            if obj is not None:
                # Append to active collection
                active_coll = context.view_layer.active_layer_collection
                active_coll.collection.objects.link(obj)
                # Append to main collection
                # context.collection.objects.link(obj)

        return {'FINISHED'}


# ------------------------------------------------------------------------
#    Panel in Object Mode
# ------------------------------------------------------------------------

class MY_PT_custom_panel(Panel):
    bl_idname = "OBJECT_PT_my_panel"
    bl_label = "My Panel"
    bl_space_type = "VIEW_3D"   
    bl_region_type = "UI"
    bl_category = "Tools"
    bl_context = "objectmode"

    def draw(self, context):
        layout = self.layout

        file_name = "link_library.blend"
        object_name_01 = "Cube"
        object_name_02 = "Sphere"

        row = layout.row()
        props = row.operator(MY_OT_custom_append.bl_idname, text="Append {}".format(object_name_01))
        props.blend = file_name
        props.object_name = object_name_01

        row = layout.row()
        props = row.operator(MY_OT_custom_append.bl_idname, text="Append {}".format(object_name_02))
        props.blend = file_name
        props.object_name = object_name_02

        layout.separator()

# ------------------------------------------------------------------------
#    Registration
# ------------------------------------------------------------------------

classes = (
    MY_OT_custom_append,
    MY_PT_custom_panel
)

def register():
    from bpy.utils import register_class
    for cls in classes:
        register_class(cls)

def unregister():
    from bpy.utils import unregister_class
    for cls in reversed(classes):
        unregister_class(cls)

if __name__ == "__main__":
    register()

Further reading: How to create a custom UI?

| improve this answer | |
$\endgroup$
  • $\begingroup$ what if I wanted to append-only Material from a blend file $\endgroup$ – Rakesh choudhary Aug 1 at 7:22
  • 1
    $\begingroup$ Is this some kind of serious question or just a statement @Rakeshchoudhary ? $\endgroup$ – brockmann Aug 2 at 2:26
  • $\begingroup$ it is a question and I wanted to know if I just wanted to append a material instead of an object. $\endgroup$ – Rakesh choudhary Aug 2 at 4:40
  • 1
    $\begingroup$ Something like data_to.materials = [name for name in data_from.materials if name.startswith("My_Mat")] should do it @Rakeshchoudhary $\endgroup$ – brockmann Aug 2 at 4:53
  • $\begingroup$ I think i also need to replace this line active_coll = context.view_layer.active_layer_collection active_coll.collection.objects.link(obj) for material and if I replace it with active_coll.collection.material.link(mat) it gives error Collection object has no attribute material $\endgroup$ – Rakesh choudhary Aug 2 at 12:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.