From 952664f36d34fe2c3d754be3aad2ae8642985374 Mon Sep 17 00:00:00 2001 From: Sergey Date: Thu, 14 Apr 2022 21:02:57 +0400 Subject: [PATCH] Copy Modifiers node (#4424) new Copy Modifiers node --- docs/make.bat | 6 +- docs/nodes/object_nodes/copy_modifiers.rst | 42 ++++++++++ .../nodes/object_nodes/object_nodes_index.rst | 1 + index.md | 1 + nodes/object_nodes/copy_modifiers.py | 76 +++++++++++++++++++ utils/handle_blender_data.py | 53 +++++++++++++ 6 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 docs/nodes/object_nodes/copy_modifiers.rst create mode 100644 nodes/object_nodes/copy_modifiers.py diff --git a/docs/make.bat b/docs/make.bat index e59e7c4c4b..2931a2a32c 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -13,7 +13,9 @@ if NOT "%PAPER%" == "" ( set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) -if "%1" == "" goto help +@REM if "%1" == "" goto help + +if "%1" == "" goto html if "%1" == "help" ( :help @@ -61,6 +63,7 @@ if errorlevel 9009 ( ) if "%1" == "html" ( + :html %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. @@ -240,3 +243,4 @@ if "%1" == "pseudoxml" ( ) :end +pause \ No newline at end of file diff --git a/docs/nodes/object_nodes/copy_modifiers.rst b/docs/nodes/object_nodes/copy_modifiers.rst new file mode 100644 index 0000000000..6c6a4e315b --- /dev/null +++ b/docs/nodes/object_nodes/copy_modifiers.rst @@ -0,0 +1,42 @@ +=================== +Copy Modifiers Node +=================== + +.. figure:: https://user-images.githubusercontent.com/28003269/163018172-7911e4a4-acd3-4cd5-a09f-1b436c9de088.png + :align: right + :figwidth: 200px + +Functionality +------------- + +The node performs similar operation to standard Blender Copy Modifiers operation. +It takes two set of objects and apply modifiers from one set to another. +It is useful for example in case when you want to assign Remesh or Subdivide modifiers +to Sverchok objects. + +.. raw:: html + + + +Inputs +------ + +**Object To** - Objects to whom to apply modifiers + +**Object From** - Objects from which get modifiers + +Outputs +------- + +**Object** - Objects with assigned modifiers + +Examples +-------- + +Assign Remesh modifier + +.. image:: https://user-images.githubusercontent.com/28003269/163020246-317c00e5-dd15-4caa-8c14-3c98ca633ae5.png + :width: 700 px diff --git a/docs/nodes/object_nodes/object_nodes_index.rst b/docs/nodes/object_nodes/object_nodes_index.rst index e03b59cdd8..5e17ac79b5 100644 --- a/docs/nodes/object_nodes/object_nodes_index.rst +++ b/docs/nodes/object_nodes/object_nodes_index.rst @@ -19,3 +19,4 @@ Objects set_loop_normals set_mesh_attribute set_collection + copy_modifiers diff --git a/index.md b/index.md index af8c123e0e..ea5fd0c182 100644 --- a/index.md +++ b/index.md @@ -645,6 +645,7 @@ SvSCNRayCastNodeMK2 SvSetLoopNormalsNode SvSetCollection + SvCopyModifiersNode ## Scene SvGetObjectsData diff --git a/nodes/object_nodes/copy_modifiers.py b/nodes/object_nodes/copy_modifiers.py new file mode 100644 index 0000000000..6e81040355 --- /dev/null +++ b/nodes/object_nodes/copy_modifiers.py @@ -0,0 +1,76 @@ +# This file is part of project Sverchok. It's copyrighted by the contributors +# recorded in the version control history of the file, available from +# its original location https://github.com/nortikin/sverchok/commit/master +# +# SPDX-License-Identifier: GPL3 +# License-Filename: LICENSE +from operator import attrgetter + +import bpy +from sverchok.data_structure import repeat_last + +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.utils.nodes_mixins.sv_animatable_nodes import SvAnimatableNode +from sverchok.utils.handle_blender_data import BlModifier + + +class SvCopyModifiersNode(SvAnimatableNode, SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: modifiers + Tooltip: + """ + bl_idname = 'SvCopyModifiersNode' + bl_label = 'Copy Modifiers' + bl_icon = 'MODIFIER_DATA' + + def sv_init(self, context): + self.inputs.new('SvObjectSocket', 'Object To') + self.inputs.new('SvObjectSocket', 'Object From') + self.outputs.new('SvObjectSocket', 'Object') + + def draw_buttons(self, context, layout): + self.draw_animatable_buttons(layout) + + def process(self): + obj_to = self.inputs['Object To'].sv_get(deepcopy=False, default=[]) + obj_from = self.inputs['Object From'].sv_get(deepcopy=False, default=[]) + + for to, _from in zip(obj_to, repeat_last(obj_from)): + + # test changes, should prevent from useless mesh reevaluations presumably + is_valid = True + for mod_from in _from.modifiers: + if mod_from.name not in to.modifiers: + is_valid = False + break + mod_to = to.modifiers[mod_from.name] + if BlModifier(mod_to) != BlModifier(mod_from): + is_valid = False + break + else: + if len(to.modifiers) != len(_from.modifiers): + is_valid = False + + # reapply modifiers + if not is_valid: + to.modifiers.clear() + for mod_from in _from.modifiers: + mod_to = to.modifiers.new(mod_from.name, mod_from.type) + + # apply modifier properties + for prop in (p for p in mod_from.bl_rna.properties if not p.is_readonly): + setattr(mod_to, prop.identifier, getattr(mod_from, prop.identifier)) + if mod_from.type == 'NODES' and mod_from.node_group: + for tree_inp in mod_from.node_group.inputs[1:]: + prop_name = tree_inp.identifier + mod_to[prop_name] = mod_from[prop_name] + mod_to[f"{prop_name}_use_attribute"] = mod_from[f"{prop_name}_use_attribute"] + mod_to[f"{prop_name}_attribute_name"] = mod_from[f"{prop_name}_attribute_name"] + for tree_out in mod_from.node_group.outputs[1:]: + prop_name = tree_out.identifier + mod_to[f"{prop_name}_attribute_name"] = mod_from[f"{prop_name}_attribute_name"] + + self.outputs['Object'].sv_set(obj_to) + + +register, unregister = bpy.utils.register_classes_factory([SvCopyModifiersNode]) diff --git a/utils/handle_blender_data.py b/utils/handle_blender_data.py index 54a9ad6f25..1012397115 100644 --- a/utils/handle_blender_data.py +++ b/utils/handle_blender_data.py @@ -108,6 +108,59 @@ def get_sv_trees(): # In general it's still arbitrary set of functionality (like module which fully consists with functions) # But here the functions are combine with data which they handle +class BlModifier: + def __init__(self, modifier): + self._mod: bpy.types.Modifier = modifier + + def get_property(self, name): + return getattr(self._mod, name) + + def set_property(self, name, value): + setattr(self._mod, name, value) + + def get_tree_prop(self, name): + return self._mod[name] + + def set_tree_prop(self, name, value): + self._mod[name] = value + + @property + def type(self) -> str: + return self._mod.type + + def __eq__(self, other): + if isinstance(other, BlModifier): + # check type + if self.type != other.type: + return False + + # check properties + for prop in (p for p in self._mod.bl_rna.properties if not p.is_readonly): + if other.get_property(prop.identifier) != self.get_property(prop.identifier): + return False + + # check tree properties + if self._mod.type == 'NODES' and self._mod.node_group: + for tree_inp in self._mod.node_group.inputs[1:]: + prop_name = tree_inp.identifier + if self.get_tree_prop(prop_name) != other.get_tree_prop(prop_name): + return False + use_name = f"{prop_name}_use_attribute" + if self.get_tree_prop(use_name) != other.get_tree_prop(use_name): + return False + attr_name = f"{prop_name}_attribute_name" + if self.get_tree_prop(attr_name) != other.get_tree_prop(attr_name): + return False + for tree_out in self._mod.node_group.outputs[1:]: + prop_name = f"{tree_out.identifier}_attribute_name" + if self.get_tree_prop(prop_name) != other.get_tree_prop(prop_name): + return False + + return True + else: + return NotImplemented + + class BlTrees: """Wrapping around Blender tree, use with care it can crash if other containers are modified a lot