[Addon] Copy Custom Properties

Hello,

i have coded a small addon which allows to copy the custom properties from the active object to the selected objects.
Simply install as usually (Install from File) and you should find a new Copy Custom Properties Panel and Button under “Object” Properties.
Its only a simple addon, but i hope it might be useful.



# ##### BEGIN GPL LICENSE BLOCK #####

#

#  This program is free software; you can redistribute it and/or

#  modify it under the terms of the GNU General Public License

#  as published by the Free Software Foundation; either version 2

#  of the License, or (at your option) any later version.

#

#  This program is distributed in the hope that it will be useful,

#  but WITHOUT ANY WARRANTY; without even the implied warranty of

#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the

#  GNU General Public License for more details.

#

#  You should have received a copy of the GNU General Public License

#  along with this program; if not, write to the Free Software Foundation,

#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

#

# ##### END GPL LICENSE BLOCK #####

 

# <pep8 compliant>

 

import bpy

 

bl_info = {

    "name": "Copy Custom Properties",

    "author": "scorpion81",

    "version": ( 1, 0, 0 ),

    "blender": ( 2, 6, 9 ),

    "location": "Object > Custom Properties Copy > Copy Custom Properties",

    "description": "Copies custom properties from active object to selected objects",

    "warning": "",

    "wiki_url": "",

    "tracker_url": "",

    "category": "Object"

}

 

def set_prop(ob, name, value):

    ob[name] = value

 

def getProps(ob):

    names = list(set(ob.keys()) - set(('cycles_visibility', '_RNA_UI')))

    values = [(name, ob[name]) for name in names]

    return values

 

class CopyCustomProperties(bpy.types.Operator):

    """Copy Custom Properties from Active to Selected objects"""

    bl_idname = "object.custom_property_copy"

    bl_label = "Copy Custom Properties"

    

    @classmethod

    def poll(cls, context):

        return context.active_object is not None

 

    def execute(self, context):

        active = bpy.context.active_object

        selected = bpy.context.selected_objects

        [[set_prop(ob, name, value) for (name, value) in getProps(active)] for ob in selected]

        

        return {'FINISHED'}

 

class CopyPanel(bpy.types.Panel):

    """Creates a Custom Property Panel in the Object properties window"""

    bl_label = "Custom Property Active to Selected"

    bl_idname = "OBJECT_PT_customprop"

    bl_space_type = 'PROPERTIES'

    bl_region_type = 'WINDOW'

    bl_context = "object"

 

    def draw(self, context):

        layout = self.layout

        layout.operator("object.custom_property_copy")

 

 

def register():

    bpy.utils.register_class(CopyCustomProperties)

    bpy.utils.register_class(CopyPanel)

 

 

def unregister():

    bpy.utils.unregister_class(CopyPanel)

    bpy.utils.unregister_class(CopyCustomProperties)

 

 

if __name__ == "__main__":

    register()




object_copy_custom_properties.zip (1.28 KB)

Version compatible with Blender 2.8:
object_copy_custom_properties_1_06.zip (2.0 KB)

scorpion81

9 Likes

this is cool
but how can use it for copy mesh props?

hey, i added (simple) support for copying object.data custom properties as well. Simply use the addon as usual, the object.data custom properties will be taken into account then automatically.
But generally there is still a flaw, i did not find out yet how to copy the min, max and tooltip values as well via python.
Seems this must be done via C somehow.

object_copy_custom_properties.zip (1.3 KB)

Hey batFINGER,

Thanks for pointing that out !

I tried the following but it seems the ‘_RNA_UI’ value is only existing at the active object or even the context object.
So the following code wont work, (trying to do this:

ob['_RNA_UI'][name] = rna

bummer. so possibly i will have to patch this (if i find it) in the C sources or… do you maybe know a more handy,
pythonic workaround ?



# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

# <pep8 compliant>

import bpy

bl_info = {
    "name": "Copy Custom Properties",
    "author": "scorpion81",
    "version": ( 1, 0, 2 ),
    "blender": ( 2, 6, 9 ),
    "location": "Object > Custom Properties Copy > Copy Custom Properties",
    "description": "Copies custom properties from active object to selected objects",
    "warning": "",
    "wiki_url": "",
    "tracker_url": "",
    "category": "Object"
}

def set_prop(ob, name, value, rna, active):
    # seems to work on active object only !!!
    #bpy.context.scene.objects.active = ob
    ob['_RNA_UI'][name] = rna
    ob[name] = value
    
    #reset active object
    #bpy.context.scene.objects.active = active
    
def getRNA(ob, name):
    #min max description settings, thanks to batFINGER !
    rna = ob['_RNA_UI'][name]
    try: 
        min = rna['min']
        max = rna['max']
        desc = rna['description']
        
    except KeyError:
        min = 0.0
        max = 1.0
        desc = ""
    
    return {'min': min, 'max': max, 'description': desc}

def getProps(ob):
    #object custom properties
    names = list(set(ob.keys()) - set(('cycles_visibility', '_RNA_UI')))    
    values = [(name, ob[name], getRNA(ob, name)) for name in names]
                
    return values

class CopyCustomProperties(bpy.types.Operator):
    """Copy Custom Properties from Active to Selected objects"""
    bl_idname = "object.custom_property_copy"
    bl_label = "Copy Custom Properties"
    
    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        active = bpy.context.active_object
        selected = bpy.context.selected_objects
        
        [[set_prop(ob, name, value, rna, active) for (name, value, rna) in getProps(active)] for ob in selected]
        [[set_prop(ob.data, name, value, rna, active) for (name, value, rna) in getProps(active.data)] for ob in selected]
        
        
        return {'FINISHED'}

class CopyPanel(bpy.types.Panel):
    """Creates a Custom Property Panel in the Object properties window"""
    bl_label = "Custom Property Active to Selected"
    bl_idname = "OBJECT_PT_customprop"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "object"

    def draw(self, context):
        layout = self.layout
        layout.operator("object.custom_property_copy")


def register():
    bpy.utils.register_class(CopyCustomProperties)
    bpy.utils.register_class(CopyPanel)


def unregister():
    bpy.utils.unregister_class(CopyPanel)
    bpy.utils.unregister_class(CopyCustomProperties)


if __name__ == "__main__":
    register()

awww, found it… i overlooked those 2 lines in your example:

if "_RNA_UI" not in self.keys():
            self["_RNA_UI"] = {}

Simple and elegant ! If that thingy is missing, just quickly build it yourself… and i wanted to mess with the C sources
because of this… :spin:

fixed version:

# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

# <pep8 compliant>

import bpy

bl_info = {
    "name": "Copy Custom Properties",
    "author": "scorpion81",
    "version": ( 1, 0, 2 ),
    "blender": ( 2, 6, 9 ),
    "location": "Object > Custom Properties Copy > Copy Custom Properties",
    "description": "Copies custom properties from active object to selected objects",
    "warning": "",
    "wiki_url": "",
    "tracker_url": "",
    "category": "Object"
}

def set_prop(ob, name, value, rna, active):
    #need to build own dict if missing
    if '_RNA_UI' not in ob.keys():
        ob['_RNA_UI'] = {}
        
    ob['_RNA_UI'][name] = rna
    ob[name] = value
    
def getRNA(ob, name):
    #min max description settings, thanks to batFINGER !
    try: 
        rna = ob['_RNA_UI'][name]
        min = rna['min']
        max = rna['max']
        desc = rna['description']
        
    except KeyError:
        min = 0.0
        max = 1.0
        desc = ""
    
    return {'min': min, 'max': max, 'description': desc}

def getProps(ob):
    #object custom properties
    names = list(set(ob.keys()) - set(('cycles_visibility', '_RNA_UI')))    
    values = [(name, ob[name], getRNA(ob, name)) for name in names]
                
    return values

class CopyCustomProperties(bpy.types.Operator):
    """Copy Custom Properties from Active to Selected objects"""
    bl_idname = "object.custom_property_copy"
    bl_label = "Copy Custom Properties"
    
    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        active = bpy.context.active_object
        selected = bpy.context.selected_objects
        
        [[set_prop(ob, name, value, rna, active) for (name, value, rna) in getProps(active)] for ob in selected]
        [[set_prop(ob.data, name, value, rna, active) for (name, value, rna) in getProps(active.data)] for ob in selected]
        
        
        return {'FINISHED'}

class CopyPanel(bpy.types.Panel):
    """Creates a Custom Property Panel in the Object properties window"""
    bl_label = "Custom Property Active to Selected"
    bl_idname = "OBJECT_PT_customprop"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "object"

    def draw(self, context):
        layout = self.layout
        layout.operator("object.custom_property_copy")


def register():
    bpy.utils.register_class(CopyCustomProperties)
    bpy.utils.register_class(CopyPanel)


def unregister():
    bpy.utils.unregister_class(CopyPanel)
    bpy.utils.unregister_class(CopyCustomProperties)


if __name__ == "__main__":
    register()

object_copy_custom_properties_1_02.zip (1.49 KB)

Glad you found it.

There is a to_dict() method that makes ID props simple to copy and manipulate, via a dictionary copy.


rna_ui = ob['_RNA_UI'].to_dict()

copy_ob['_RNA_UI'] = rna_ui

# or 

copy_ob['_RNA_UI'][prop] = rna_ui.setdefault('prop', {})

Also I still think you should look at the bl_rna properties, eg


>>> bpy.types.Object.xxx = bpy.props.IntProperty(min=0, max=10)
>>> C.object.xxx = 4
>>> C.object['xxx']
4


>>> 'xxx' in C.object.keys()
True


>>> 'xxx' in C.object.bl_rna.properties.keys()
True


>>> C.object.bl_rna.properties['xxx'].soft_max
10

If the custom property is in bl_rna.properties.keys() then as long as the type is the same you needn’t copy the _RNA_UI.
If the types are different then you can use the values in bl_rna.properties[key] to produce a _RNA_UI

Also prob pay to do some simple type checking on the value, as min max are not needed for bools and strings.

Lastly and one of my favourite things to do is you can jam anything into the _RNA_UI dictionary, as long as it’s a simple type (float, int, bool, str) a very handy place to store things that don’t need user I/O.

PS

A quick look at your code and it will fail on empties that don’t have obj.data. Prob want to test for that.

thank you guys

Thank you for this script it came in handy! This should be included with Blender.

Any chance we could get an update to this that works with pose bones?

here is an update which should work with bones, edit_bones and… pose bones.

object_copy_custom_properties_1_03.zip (1.76 KB)

The copy button is still located at the object property panel only, but it affects object data and now armature bones too.
I only quickly tested with 2 bones of different armatures, hope that was the expected kind of usage ?
(copying custom props of all bones of active armature to selected armature and the respective bones)
Best is of course if both armatures have the same amount of bones, the copy algorithm is rather dumb… atleast there should
be no errors if thats not the case, but results may be unexpected…

Hello,

this version supports copying custom props of an active posebone / editbone / bone to selected posebones / editbones /bones inside the same armature additionally. Under bone context you find an according panel with a new operator button on it.

object_copy_custom_properties_1_04.zip (1.94 KB)

super useful! it still work on 2.76 but even that I got this error:

bpy.ops.group.objects_remove(group=’<UNKNOWN ENUM>’)
Traceback (most recent call last):
File “C:\Users\Max\AppData\Roaming\Blender Foundation\Blender\2.76\scripts\addons\object_copy_custom_properties.py”, line 107, in execute
[[set_prop_bones(ob.data.bones, data) for (data) in getPropBones(active.data.bones)] for ob in selected]
File “C:\Users\Max\AppData\Roaming\Blender Foundation\Blender\2.76\scripts\addons\object_copy_custom_properties.py”, line 107, in <listcomp>
[[set_prop_bones(ob.data.bones, data) for (data) in getPropBones(active.data.bones)] for ob in selected]
AttributeError: ‘Mesh’ object has no attribute ‘bones’

location: <unknown location>:-1

Hi,

i think this update should fix that error. It just missed checks whether the objects are actually armatures or not.

object_copy_custom_properties_1_05.zip (1.98 KB)

Handy and usefull tool. Thanks

Thank you so much !

Small update for blender 2.8, just changed the required blender version from (2,7,7) to (2,80,0) only, and bumped the addon version number from (1,0,5) to (1,0,6). Should work as before.

object_copy_custom_properties_1_06.zip (2.0 KB)

3 Likes

Thank you for this, looks super useful

Very useful indeed! Could you also add a button to copy properties from other places, like scene custom properties to object custom properties, please?!?

Thank you! It was useful for my project.

Thank you and marry me please, i am creating a character with about 15 items that should be able to move freely, parent to the body and both of the hands, i was about to lose it when i realized the amount of custom properties to drive constrains i would have to create over and over. Thanks again!