11
$\begingroup$

Is there a better way to copy a modifier with all attributes from active object to selected object through python scripting other than adding a modifier to selected and copy attributes one by one (with dictionary for example) from active?

The problem is some attributes are read-only (even if it makes no sense - like for example subdivision algorithm in Subsurf modifier) and i don't know how to set them through python script. So i can't copy that subdivision algorithm value from one to another through scripting with method setting a value for an attribute:

bpy.data.objects[object_name].modifiers[modifier_name].attribute_name = attribute_value

So to reiterate my question: is there any other way to add a modifier to an object that would allow setting up values for all attributes?

$\endgroup$
2
  • $\begingroup$ You could copy the object (incl. modifiers) and let a script set all non-modifier related properties to the target object's properties, but it's basically the same as copying modifier attributes... $\endgroup$
    – CodeManX
    Nov 15, 2013 at 1:38
  • $\begingroup$ You may want to investigate the source of the Copy Attributes add-on. It provides the functionality of copying modifiers. At the very least, you can see how it's done there. $\endgroup$
    – Fweeb
    Nov 15, 2013 at 18:38

5 Answers 5

19
$\begingroup$

I don't think that's possible.

A modifier can only be inserted through modifiers.new, so its properties do need to be copied individually. The problem here is in checking which properties can be copied. (e.g., Subsurf modifier's subdivision_type property is actually not readonly as you claimed, but we'd need to test it manually or check the API reference to find out.)

The whole "check writability then copy" procedure can be done more efficiently, though. For example, see this function:

import bpy

def main():
    active_object = bpy.context.object
    selected_objects = [o for o in bpy.context.selected_objects
                        if o != active_object and o.type == active_object.type]

    for obj in selected_objects:
        for mSrc in active_object.modifiers:
            mDst = obj.modifiers.get(mSrc.name, None)
            if not mDst:
                mDst = obj.modifiers.new(mSrc.name, mSrc.type)

            # collect names of writable properties
            properties = [p.identifier for p in mSrc.bl_rna.properties
                          if not p.is_readonly]

            # copy those properties
            for prop in properties:
                setattr(mDst, prop, getattr(mSrc, prop))

main()

It copies all modifiers from active object, to every other selected objects of similar type. What modifier properties to copy is determined by checking is_readonly attribute of all items in the modifier's bl_rna.properties. This way, we can ensure that only writable properties get copied.

In the API Reference, some readonly attributes like Dynamic Paint's brush_settings have no other method of access, so they can be set only through the UI.

$\endgroup$
3
  • $\begingroup$ Thanks for clarifying. For some reason i still can't write some attributes with my code (i've simply used try/except to avoid read-only attributes, so there must be other reason that prevented some of the attributes from updating), but your code works without a problem. Thank you. $\endgroup$
    – kilbee
    Nov 15, 2013 at 12:24
  • $\begingroup$ Truly awesome answer! $\endgroup$
    – Antoni4040
    Jun 10, 2014 at 12:01
  • $\begingroup$ great answer!!!! $\endgroup$
    – Chris
    Aug 28, 2021 at 16:34
6
$\begingroup$

A simpler method than Adhi's is to use make_links_data

Say you make two cubes. You apply modifiers to the first one and want to copy them to the second one. Then your code should look something like this:

import bpy

mod_obj = bpy.context.scene.objects["Cube"]           # cube with mods
modable_obj = bpy.context.scene.objects["Cube.001"]   # cube without mods

mod_obj.select = True
bpy.context.scene.objects.active = mod_obj
modable_obj.select = True

bpy.ops.object.make_links_data(type='MODIFIERS')

bpy.ops.object.select_all(action='DESELECT')
$\endgroup$
1
$\begingroup$

Based on Adhi's answer.

Maybe below can be useful for someone in the future. Can be more Pythonic and beautiful, but I think below should collect all the Modifier Props from ob for obtarget, providing obtarget already has all the Modifiers being generated based on ob.

import bpy

ob = bpy.context.active_object
obtarget = bpy.data.objects['Cube.001']

mod_props = []

for mod in ob.modifiers:
    properties = []
    for prop in mod.bl_rna.properties:
        if not prop.is_readonly:
            properties.append(prop.identifier)
    mod_props.append([mod, properties])

for stuff in mod_props:
    for prop in stuff[1]:
        setattr(obtarget.modifiers[stuff[0].name], prop, getattr(stuff[0], prop))
$\endgroup$
1
$\begingroup$

I attempted to refactor Adhi's answer to a class for easier usage:

from typing import Any, NamedTuple

import bpy


class ModifierSpec(NamedTuple):
    name: str
    type: str
    properties: dict[str, Any]

    @classmethod
    def from_modifier(cls, modifier):
        spec = cls(modifier.name, modifier.type, {})

        for prop in modifier.bl_rna.properties:
            if prop.is_readonly:
                continue

            name = prop.identifier
            spec.properties[name] = getattr(modifier, name)

        return spec

    def add_to_object(self, obj):
        new_modifier = obj.modifiers.get(self.name, None)
        if new_modifier is None:
            new_modifier = obj.modifiers.new(self.name, self.type)

        for name, prop in self.properties.items():
            setattr(new_modifier, name, prop)


# collect modifiers from object
modifier_specs: list[ModifierSpec] = []
for old_modifier in source_obj.modifiers:
    modifier_specs.append(ModifierSpec.from_modifier(old_modifier))

# add modifiers from spec
for spec in modifier_specs:
    spec.add_to_object(dest_obj)
$\endgroup$
1
  • $\begingroup$ Looks nice and tidy. You might want to add a guard clause somewhere if the user tries to copy modifiers to an object that doesn't support it like an empty object, since new_modifier will then be None and the subsequent lines will throw an error. Thank you for sharing ! $\endgroup$
    – Gorgious
    May 11, 2023 at 7:03
0
$\begingroup$

Another solution which uses an operator override of object.modifier_copy_to_selected and doesn't require you to select objects.

import bpy

source = bpy.data.objects["Cube"]
target = bpy.data.objects["Cube.001"]
modifier_name = "Solidify"

with bpy.context.temp_override(object=source, selected_objects=(source, target)):
    bpy.ops.object.modifier_copy_to_selected(modifier=modifier_name)
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .