From e70eb29521263260ffc4044ad0e8a2adfebe04e2 Mon Sep 17 00:00:00 2001 From: Durman Date: Tue, 12 Apr 2022 22:18:07 +0400 Subject: [PATCH 1/2] 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 | 65 +++++++++++++++++++ 5 files changed, 114 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 e59e7c4c4..2931a2a32 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 000000000..6c6a4e315 --- /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 e03b59cdd..5e17ac79b 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 ea09bf1fb..c31cd6b84 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 000000000..d3af97000 --- /dev/null +++ b/nodes/object_nodes/copy_modifiers.py @@ -0,0 +1,65 @@ +# 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 + + +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] + for prop in (p for p in mod_from.bl_rna.properties if not p.is_readonly): + if getattr(mod_to, prop.identifier) != getattr(mod_from, prop.identifier): + 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: + new_mod = to.modifiers.new(mod_from.name, mod_from.type) + for prop in (p for p in mod_from.bl_rna.properties if not p.is_readonly): + setattr(new_mod, prop.identifier, getattr(mod_from, prop.identifier)) + + self.outputs['Object'].sv_set(obj_to) + + +register, unregister = bpy.utils.register_classes_factory([SvCopyModifiersNode]) -- GitLab From 0d1b21e4ab8b42766357743fabe6c3046e58f383 Mon Sep 17 00:00:00 2001 From: Durman Date: Wed, 13 Apr 2022 22:19:28 +0400 Subject: [PATCH 2/2] handle the case when the modifier is of GeometryNodes type --- nodes/object_nodes/copy_modifiers.py | 23 ++++++++---- utils/handle_blender_data.py | 53 ++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/nodes/object_nodes/copy_modifiers.py b/nodes/object_nodes/copy_modifiers.py index d3af97000..6e8104035 100644 --- a/nodes/object_nodes/copy_modifiers.py +++ b/nodes/object_nodes/copy_modifiers.py @@ -11,6 +11,7 @@ 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): @@ -43,10 +44,9 @@ class SvCopyModifiersNode(SvAnimatableNode, SverchCustomTreeNode, bpy.types.Node is_valid = False break mod_to = to.modifiers[mod_from.name] - for prop in (p for p in mod_from.bl_rna.properties if not p.is_readonly): - if getattr(mod_to, prop.identifier) != getattr(mod_from, prop.identifier): - is_valid = False - break + if BlModifier(mod_to) != BlModifier(mod_from): + is_valid = False + break else: if len(to.modifiers) != len(_from.modifiers): is_valid = False @@ -55,9 +55,20 @@ class SvCopyModifiersNode(SvAnimatableNode, SverchCustomTreeNode, bpy.types.Node if not is_valid: to.modifiers.clear() for mod_from in _from.modifiers: - new_mod = to.modifiers.new(mod_from.name, mod_from.type) + 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(new_mod, prop.identifier, getattr(mod_from, prop.identifier)) + 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) diff --git a/utils/handle_blender_data.py b/utils/handle_blender_data.py index 54a9ad6f2..101239711 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 -- GitLab