Append objects from blend (yes, i know... i know...)

Hello, fellow Blender enthusiasts!

I would really need some help with this. Theoretically, it should work, but i’m not that well versed in python, so I may be wrong.
I have been trying to append objects from different blend files and I can do it, but it isn’t convenient. What I really would like to accomplish is to get two drop-down menus, where one of them refers to blend file name, the other to the objects (or other data) in that particular blend file. That sort of script would surely be useful.

The author in the link above provides really good way of accessing file_name and a lot more, but I can’t get the object names the way I want them (well, I can, but the script becomes too “Brobdingnagian”, so I can’t).

http://wiki.blender.org/index.php/Dev:2.5/Py/Scripts/Cookbook/Code_snippets/Interface#Dynamic_drop-down_menus

The other link (above this paragraph) provides excellent drop_down menu with Boolean property instead of endless if statements, but I can’t get these two ideas to mesh together and I’m unsure about that cross-referencing part (file_name --> object_name), but I’ve seen something similar in sapling_tree script, so it should work.

I believe that the end result could be very useful, the library blends could be accessed very efficiently. Right now, the only way i can get the result is to manually write the list of file names and other list with objects every time I add the object to the library.blends.

Any thoughts?

Got this to work quite well:

import bpy
from os.path import dirname, basename

filepath = r"C:\Users\CoDEmanX\Desktop\Blender\CoD Blender	est_lib.blend"

datablock_dir = "/Object/"
filename = "//" + basename(filepath) + datablock_dir
directory = filepath + datablock_dir


class SCENE_OT_object_lib_refresh(bpy.types.Operator):
    bl_label = "Refresh Library"
    bl_idname = "scene.object_lib_refresh"
    
    def execute(self, context):
        SCENE_PT_object_lib.update = True
        SCENE_PT_object_lib.refreshed = False
        return {'FINISHED'}


class SCENE_PT_object_lib(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Hello World Panel"
    bl_idname = "SCENE_PT_object_lib"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "scene"
    
    update = True
    refreshed = False
    obs = []

    def draw(self, context):
        layout = self.layout
        layout.prop(context.window_manager, "ob_lib")
        if self.__class__.refreshed:
            layout.label("Error, refreshed list, try again.", icon='INFO')
        layout.operator(SCENE_OT_object_lib_refresh.bl_idname)
        

def update_cb(self, context):
    if bpy.ops.wm.link_append.poll():
        bpy.ops.wm.link_append(filepath=filename, directory=directory, 
                               filename=self.ob_lib, link=False)
        new_obs = [ob for ob in context.scene.objects if ob.select]
        if len(new_obs) > 0:
            context.scene.objects.active = new_obs[0]
            SCENE_PT_object_lib.refreshed = False
        else:
            bpy.ops.scene.object_lib_refresh()
            SCENE_PT_object_lib.refreshed = True
    
def item_cb(self, context):
    if SCENE_PT_object_lib.update:
        SCENE_PT_object_lib.obs = []
        
         # print list of materials with users, if present.
        with bpy.data.libraries.load(filepath) as (data_from, data_to):
            if data_from.objects:
                for ob in data_from.objects:
                    SCENE_PT_object_lib.obs.append((ob, ob, ''))
        SCENE_PT_object_lib.update = False
        
    return SCENE_PT_object_lib.obs
        

    
def register():
    bpy.utils.register_class(SCENE_PT_object_lib)
    bpy.utils.register_class(SCENE_OT_object_lib_refresh)
    bpy.types.WindowManager.ob_lib = bpy.props.EnumProperty(items=item_cb, update=update_cb)


def unregister():
    bpy.utils.unregister_class(SCENE_PT_object_lib)
    bpy.utils.unregister_class(SCENE_OT_object_lib_refresh)
    del bpy.types.WindowManager.ob_lib


if __name__ == "__main__":
    register()


You should only need to change the filepath variable at the top and set it to your lib.

There’s a refresh button, although there’s auto-refresh if append failed - could actually drop either of them.

CoDEmanX, You are a life saver! That is pretty much what I meant and it works brilliantly.

I’ll try to combine it with the ideas mentioned in my previous post and get back here with the result if I succeed. So excited, so excited

I had a minor success today. I managed to get file names to show up in a drop down menu right next to object append menu by slightly modifying script provided by CoDEmanX. There was a little bit of cheating involved, must admit, but i got desperate. And, it seems, there is just one way to get this thing to work together with object drop down menu and that involves cheating again.

File menu had to be defined in register function as scene property. Everything else I knew and could find on the internet failed miserably. And as You might imagine, it switches between file names, but doesn’t actually do anything else.

As for the functionality there is one solution I haven’t tried yet, but it should work. Everything outside blender context is accessed through file path, am I correct? My idea is to get selection info out of that file drop down list and nest it into the file path that blender will read. Would that work? Are there better solutions?

Here I set up addon folder called megappend. There You’ll find 2 blend files in folder called Primitives and the script. Those who would like to take a look at that mess, can paste it in their Blender addon folder.

https://dl.dropboxusercontent.com/u/95425373/megappend.zip

Alternatively, here is a script for those, who instantly can spot my wrong doings without even launching it:

http://www.pasteall.org/47850/python

Sorry, for triple posting, but I got it to work! I re-uploaded the addon.zip and updated the script in pasteall. It still isn’t finished, it requires You to rerun the script for the changes in file menu to take effect, but that is fixable, isn’t it?
The menu is located in the Scene Properties panel.

Right now it supports multiple blend files in one directory and blend data in those blend files. It creates a list of blend file names located in the specified directory and updates that list. That part of the script was made by zeffi or Dealga McArdle for his material appender.

The example is in the zip file. There are two files: primitive1.blend and primitive2.blend. Each contains at least 5 objects. You can switch between files and between objects, but after you swithed the filename, You need to rerun the script for the object list to refresh.

Is it supposed to support multiple library blend filepaths, and does it need to be recursive? Or either of both?

How many blends with how many Objects will there be?

A suggestion re file paths.

Make an addon preferences class, with a subtype DIR_PATH for folders, FILE_PATH for files. This will show up in a panel under the registration screen.


class GDALAddonPreferences(AddonPreferences):
    bl_idname = "GDAL_tools"


    base_folder = StringProperty(
            name="GDAL Tools Path",
            subtype='DIR_PATH',
            )


    gimp_binary = StringProperty(
            name="Gimp File Path",
            subtype='FILE_PATH',
            description="Path to Gimp Executable",
            )


    def draw(self, context):
        layout = self.layout
        layout.label(text="GDAL Tools", icon='WORLD')
        layout.prop(self, "base_folder")
        layout.prop(self, "gimp_binary")




When the file is set up as an addon you can retrieve the path with.


    Dir = os.path.dirname(__file__)
    path = os.path.join(Dir, 'settings.xml')

The default of the folder stringprop can be set using this.

Another thought is setting up a filebrowser to select the files, here you could associate which collection you want to link/append from which file. Below is a simple filebrowser script that lists the selected files on the Panel.


import bpy


# ExportHelper is a helper class, defines filename and
# invoke() function which calls the file selector.


from bpy.props import StringProperty, BoolProperty, EnumProperty, CollectionProperty
from bpy.types import Operator
from bpy_extras.io_utils import ImportHelper


class SetLibraryFile(Operator, ImportHelper):
    """This appears in the tooltip of the operator and in the generated docs"""
    bl_idname = "export_test.some_data"  # important since its how bpy.ops.import_test.some_data is constructed
    bl_label = "Set Library File"
    files = CollectionProperty(type=bpy.types.OperatorFileListElement,
                               options={'HIDDEN',
                                        'SKIP_SAVE'})


    directory = StringProperty(maxlen=1024, subtype='DIR_PATH',
                               options={'HIDDEN', 'SKIP_SAVE'})    
    ''' filter for blend files only'''
    filter_blender = BoolProperty(default=True, options={'HIDDEN'})
    '''
    filter_glob = StringProperty(
            default="*.txt",
            options={'HIDDEN'},
            )
    '''
    # List of operator properties, the attributes will be assigned
    # to the class instance from the operator settings before calling.
    use_setting = BoolProperty(
            name="Example Boolean",
            description="Example Tooltip",
            default=True,
            )


    type = EnumProperty(
            name="Example Enum",
            description="Choose between two items",
            items=(('OPT_A', "First Option", "Description one"),
                   ('OPT_B', "Second Option", "Description two")),
            default='OPT_A',
            )


    def draw(self, context):
        layout = self.layout
        row = layout.row()
        row.operator("file.select_all_toggle")
        for file in self.files:
            row = layout.row()
            row.label(file.name)
            
    def invoke(self, context, event):
        wm = context.window_manager
        wm.fileselect_add(self)
        print("FILE SELECTOR INVOKED...................")
        return {'RUNNING_MODAL'}
    
    def execute(self, context):
        return {'FINISHED'}




def register():
    bpy.utils.register_class(SetLibraryFile)




def unregister():
    bpy.utils.unregister_class(SetLibraryFile)




if __name__ == "__main__":
    register()


    # test call
    bpy.ops.export_test.some_data('INVOKE_DEFAULT')

Hello, bright minds of this community!

Thank You, batFINGER for those scripts! It took this addon to another level of user friendliness, although I was able to incorporate only the file path part of the script. The Addon Preferences doesn’t work, keeps printing errors to the console that Addon Preferences not specified, even if I import AddonPreferences from bpy.types. I haven’t specified any variables in the class, but it even doesn’t get to the reading part and I haven’t explored it in great depths yet. If You could take a look at script, it would be greatly appreciated.

https://dl.dropboxusercontent.com/u/95425373/megappend.zip

Here is the addon.zip, paste it in blender/scripts/addons folder or anywhere You prefer, although the default file path is set there when not specified otherwise. The script is included, You might need to run it manually, haven’t tested it through User Preferences. There is a folder named Primitives with 4 blend files. You can paste more files. File names are random, no need to stick to some naming conventions. There is a file browse operator, so You can specify different directories. Rerun of the script is needed when file name is selected from drop down menu. Haven’t fixed that problem yet and I don’t know how. So, if anyone comes with the solution, the fame and glory is Yours shared with CoDEmanX, batFINGER, zeffi and ideasman_42.

Here is a script in it’s current state:
http://www.pasteall.org/47874/python

P.S. It turns out, that currently script doesn’t work. I’ll re-upload, when it does and remove this warning. Sorry for inconvenience. As I said, I’m really bad at this.

kakts,

don’t have time to look at this too thoroughly

couple of quick things tho, windows file-paths in strings need to be double escaped with \ otherwise you get weird results, for instance
is a newline is a tab in a string.
and \ will not be interpreted as the first slosh cancels the second. The way it is now makes my computer beep.

You need an init.py in your megappend folder if you are going to run it as an addon. Here is one, after you put this in your megappend folder, (I’ve had to take the meg_append module out as it is giving too much grief,) it shows how to set up addon preferences. The base_folder is set to the folder the addon is sitting in plus “Primitives” Hit F8 to reload addons after you put the init in the folder.


bl_info = {
    "name": "Mega Append",
    "author": "kakts",
    "location": "Properties > Scene",
    "description": "Mega Append ",
    "warning": "Still in Testing",
    "wiki_url": "",
    "version": (0, 1),
    "blender": (2, 6, 9),
    "tracker_url": "",
    "support": 'TESTING',
    "category": "Scene"}




# meg_append is giving me grief uncomment when OK
'''
if "bpy" in locals():
    import imp
    imp.reload(meg_append)
else:
    from . import meg_append
'''


import bpy
from bpy.types import AddonPreferences
from bpy.props import StringProperty
import os


default_primitive_dir = os.path.join(os.path.dirname(__file__), "Primitives")

class MegappendAddonPreferences(AddonPreferences): #(source provided by BatFINGER)
    bl_idname = "megappend"


    base_folder = StringProperty(
            name="MegAppend_Tools_Path",
            subtype='DIR_PATH',
            default = "%s" % default_primitive_dir
            )

    blend_binary = StringProperty(
            name="blend File Path",
            subtype='FILE_PATH',
            description="Path to blend file"
            )
           
    def draw(self, context):
        layout = self.layout
        layout.label(text="MegAppend_Tools", icon='PLUGIN')
        layout.prop(self, "base_folder")
        layout.prop(self, "blend_binary")

def register():
    bpy.utils.register_class(MegappendAddonPreferences)
    #meg_append.register()

def unregister():
    bpy.utils.register_class(MegappendAddonPreferences)
    #meg_append.unregister()

To address in blender


>>> user_preferences = C.user_preferences
>>> addon_prefs = user_preferences.addons["megappend"].preferences
>>> addon_prefs.base_folder
'C:\\Documents and Settings\\batFINGER\\Application Data\\Blender Foundation\\Blender\\2.69\\scripts\\addons\\megappend\\Primitives'


>>> 

In code in your addon folder you can use


user_preferences = context.user_preferences
addon_prefs = user_preferences.addons[__name__].preferences
layout.prop(addon_prefs, "base_folder")  # draw method etc



Thanks, batFINGER!
I was having a crisis with all those beebs going off. Previosly it worked, so it was baffling to say the least. Will do all of the above and post soon, or rather edit this post.

NEWS
I’ve been living and scripting in a cave apparently, I missed out on new Blender versions, so people, who tried it on Blender 2.69 most likely thought I was delusional, when i reported it somewhat working (on Blender 2.64). Sorry about that. Hope there were no computer casualties.
To get AddonPreferences class, I migrated to new version.

NEWS II
Got the functionality back. It was as simple as changing directions of slashes from “//” to “\” and adding some slashes to the end of file path. Now it works on Blender 2.69. As soon as I get it to register through User Preferences and perform error checking multiple times, I’ll post it here.

Went close to sending a screenshot of the addon’s preference panel. Now that you have a version with that class, do you agree it’s a logical place to assign the default folder. I

Also have you considered the script using the gui of the filebrowser? One very handy feature is the thumbnail view, You can

I’ve run out of ideas again. I cannot register addon, because I have this problem now:

http://lists.blender.org/pipermail/bf-python/2012-December/005987.html
http://wiki.blender.org/index.php/Extensions:2.6/Py/API_Changes#Restricted_Context

Hello!

Finally had some spare time and I spent it on meg_append. It’s giving me grief, but thanks to batFINGER, it registers. Addon Preferences are working and You are right it is a logical place to assign default folder from. I will be happy to explore possibilities the filebrowser GUI provides as soon as we get this thing working as intended.

Right now this script provides just an interface, it doesn’t give out the variables needed for append_link operator, not sure why. The way properties are handled is giving me a great deal of headache. Not sure how to get those properties outside the class. Anyhow, here is the script:

https://dl.dropboxusercontent.com/u/95425373/megappend.zip

Any criticisms, opinions and ideas are welcome. Thank You for Your time and good luck!

My suggestion here is start a clean file. Go for a KISS approach, get One folder, One data_type, selected files from that folder workin … extend functionality once that is going.

There are references to CoDEmanX’s Operators / Panels from post #2 all thru your megappend.py but they are not included in your source.

Add that file to your addons folder, put the module name in init.py and call the register method for it. (copy the meg_append imp import and register for what ever you call cmx’s file)

then get rid of this part




filepath = r"C:\Users\CoDEmanX\Desktop\Blender\CoD Blender	est_lib.blend"

datablock_dir = "/Object/"
filename = "//" + basename(filepath) + datablock_dir
directory = filepath + datablock_dir

Change all references to them in the code to match your properties.

For instance you have scene.MyString set to filepath… Maybe choose a better name. or just use the addonprefs value… (example below in a draw method)

To set the library folder just use a stringprop subtype=‘DIR_PATH’ set up like in the preferences. (example below in a draw method) Currently you are using a file selector to select and set a path.

The filebrowser I posted in post 7 shows you how to select multiple files.

btw glob="*.blend" will give icon-less blend files… just use the filter_blender=True part.

Changing the execute(self, context) in post 7 to


    def execute(self, context):
        print("-"*80)
        print("Directory: %s" % self.directory)
        print("-"*80)     
        for file in self.files:
            print(file.name)
        print("-"*80)        
        return {'FINISHED'}

and it prints all files selected to the console… how about feeding them into your filelist from here? or feeding them to CodeMANX’s ops.

Also it prints the selected filenames on the panel in the filebrowser. ** You could select the data_type on a per-file or per- folder basis here… … later. *****

In your draw method.


        user_preferences = context.user_preferences
        addon_prefs = user_preferences.addons["megappend"].preferences
        layout.prop(addon_prefs, "base_folder")  #selects the folder
        
        op = row.operator("export_test.some_data", icon='IMASEL')  #opens that folder to select files
        op.directory = addon_prefs.base_folder

Ok that’s enough to go on with. And don’t worry bandaiding bandaging patching up spagghetting code to make it work is something I am more than guilty of,… I’ve found the hard way that the clean start I feared saved time in the long run.

***** that’s how I produce a mess.

PS don’t worry about the source provided by me thingo… I copied most of that from the templates folder and made a few adjustments.

Yes, batFINGER! YEEESSS! We did it! It works pretty well at that! There is one minor error at the beginning of operation, because I didn’t set the file path to use user preferences yet, but nothing major. Once You set the directory of Your blend library, that error goes away. There’s plenty of stuff left to do, but You can at least play with it. Here it is:

https://dl.dropboxusercontent.com/u/95425373/megappend.zip

EDIT:
Updated the script, so now there are no errors. Migrated the panel to Tool Properties (left side panel). Bravely changed the application version from 0.1 to 0.5 :slight_smile:

Script is in the link above.

Next time I’ll post, I’ll add additional functionality, such as data_type selector, file browser thumbnail view, and user defined file selection list (it is there already, but not used right now).

I consider this question and thread solved. Thank You batFINGER and CoDEmanX! This little tool owes it’s existence to You two.

Hello!
Posting at this rate makes me look bad, sorry about that, but i won’t be able to do anything Blender related this week - I’ll be sunk in Christmas depression. So I wish You all Merry Christmas, happy holidays!

Here is the latest evolution of this tool:

https://dl.dropboxusercontent.com/u/95425373/megappend_feutures.zip

It is capable of appending objects, materials, scenes and worlds now. In order to get multiple worlds in one blend, You will have to set fake users for unused world item or it will be deleted once You close Your blend file. This script works on Blender 2.69, it might not work on older builds, because it uses Addon Preferences.

This tool is really good at creating mess in Your blend file, it populates list of materials pretty fast, but in my tests I haven’t managed to crash it yet, so use it on some test subjects first. Blend files included with this script, although boring, might be useful for this purpose.

I tried to implement thumbnails, but I failed. They do show up in file browser if enabled manually, but couldn’t access them through my Set Library class. I know this setting can be specified during operator call, but this operator is executed already, so I’m a bit lost on this one.

Only saw your addon now, really interesting. I Hope you survived your Christmas depression. Could you or someone good enough for that finish that addon to add the objects to the Shift+A menu ? Or at least give some advices on how to use your/his work to populate a Shift+A submenu (like along Mesh/curves/…/prim.blend/prim2.blend that would propose to link the objects in those files), I could try then to finish it.

append or prepend draw functions to bpy.types.INFO_MT_curve_add, or bpy.types.INFO_MT_mesh_add etc.

import bpy

def draw_func(self, context):
    layout = self.layout
    layout.operator("object.select_all", text="ADD OPERATOR HERE")
bpy.types.INFO_MT_mesh_add.append(draw_func)

Hey CoDEmanX :slight_smile:

Thanks for the code, it works pretty well for static files, I have to think more about how to do it with user defined files. I’m pretty busy at the moment but I’ll try to get something usable.

Cheers

hi, I use this method here: append_model
although i’ve been told it’s better to use linked library code for this stuff.