Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Copy Modifiers node #4424

Merged
merged 2 commits into from
Apr 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion docs/make.bat
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -61,6 +63,7 @@ if errorlevel 9009 (
)

if "%1" == "html" (
:html
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
if errorlevel 1 exit /b 1
echo.
Expand Down Expand Up @@ -240,3 +243,4 @@ if "%1" == "pseudoxml" (
)

:end
pause
42 changes: 42 additions & 0 deletions docs/nodes/object_nodes/copy_modifiers.rst
Original file line number Diff line number Diff line change
@@ -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

<video width="700" controls>
<source src="https://user-images.githubusercontent.com/28003269/163026163-a8762f52-b6f2-4dcd-b95e-760da3af033a.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>

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
1 change: 1 addition & 0 deletions docs/nodes/object_nodes/object_nodes_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ Objects
set_loop_normals
set_mesh_attribute
set_collection
copy_modifiers
1 change: 1 addition & 0 deletions index.md
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,7 @@
SvSCNRayCastNodeMK2
SvSetLoopNormalsNode
SvSetCollection
SvCopyModifiersNode

## Scene
SvGetObjectsData
Expand Down
76 changes: 76 additions & 0 deletions nodes/object_nodes/copy_modifiers.py
Original file line number Diff line number Diff line change
@@ -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])
53 changes: 53 additions & 0 deletions utils/handle_blender_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down