Коммит a08ea9d0 создал по автору Durman's avatar Durman
Просмотр файлов

Socket_conversions module refactoring

владелец ed954eb8
......@@ -18,6 +18,7 @@
from copy import deepcopy
from enum import Enum
from sverchok.core.sv_custom_exceptions import ImplicitConversionProhibited
from sverchok.data_structure import get_data_nesting_level, is_ultimately
from sverchok.utils.field.vector import SvVectorField, SvMatrixVectorField, SvConstantVectorField
from sverchok.utils.field.scalar import SvScalarField, SvConstantScalarField
......@@ -28,23 +29,39 @@ from sverchok.utils.solid_conversion import to_solid_recursive
from mathutils import Matrix, Quaternion
from numpy import ndarray
SOCKET_TYPES = {
'v': 'SvVerticesSocket',
's': 'SvStringsSocket',
'm': 'SvMatrixSocket',
'q': 'SvQuaternionSocket',
'c': 'SvColorSocket',
'vf': "SvVectorFieldSocket",
'sf': 'SvScalarFieldSocket'
}
def test_socket_type(self, other, A, B):
""" A is origin type, B is destination type """
return other.bl_idname == SOCKET_TYPES[A] and self.bl_idname == SOCKET_TYPES[B]
def matrices_to_vfield(data):
if isinstance(data, Matrix):
data = deepcopy(data)
return SvMatrixVectorField(data)
if isinstance(data, (list, tuple)):
return [matrices_to_vfield(item) for item in data]
raise TypeError("Unexpected data type from Matrix socket: %s" % type(data))
def vertices_to_vfield(data):
if isinstance(data, (tuple, list)) and len(data) == 3 and isinstance(data[0], (float, int)):
data = deepcopy(data)
return SvConstantVectorField(data)
elif isinstance(data, (list, tuple)):
return [vertices_to_vfield(item) for item in data]
else:
raise TypeError("Unexpected data type from Vertex socket: %s" % type(data))
def numbers_to_sfield(data):
if isinstance(data, (int, float)):
return SvConstantScalarField(data)
elif isinstance(data, (list, tuple)):
return [numbers_to_sfield(item) for item in data]
else:
raise TypeError("Unexpected data type from String socket: %s" % type(data))
# ---
def get_matrices_from_locs(data):
def vectors_to_matrices(source_data):
"""This means we're going to get a flat list of the incoming
locations and convert those into matrices proper."""
location_matrices = []
collect_matrix = location_matrices.append
......@@ -57,11 +74,23 @@ def get_matrices_from_locs(data):
else:
get_all(item)
get_all(data)
get_all(source_data)
return location_matrices
def get_matrices_from_quaternions(data):
def matrices_to_vectors(source_data):
locations = []
collect_vector = locations.append
def get_all(data):
for sublist in data:
collect_vector(sublist.to_translation()[:])
get_all(source_data)
return [locations]
def quaternions_to_matrices(source_data):
matrices = []
collect_matrix = matrices.append
......@@ -73,11 +102,11 @@ def get_matrices_from_quaternions(data):
else:
get_all(item)
get_all(data)
get_all(source_data)
return matrices
def get_quaternions_from_matrices(data):
def matrices_to_quaternions(source_data):
quaternions = []
collect_quaternion = quaternions.append
......@@ -86,115 +115,31 @@ def get_quaternions_from_matrices(data):
q = tuple(mat.to_quaternion())
collect_quaternion(q)
get_all(data)
get_all(source_data)
return [quaternions]
def is_matrix(mat): # doesn't work with Mathutils.Matrix ?
''' expensive function call? '''
if not isinstance(mat, (tuple, list)) or not len(mat) == 4:
return
for i in range(4):
if isinstance(mat[i], (tuple, list)):
if not (len(mat[i]) == 4 and all([isinstance(j, (float, int)) for j in mat[i]])):
return
else:
return
return True
def get_locs_from_matrices(data):
locations = []
collect_vector = locations.append
def get_all(data):
for sublist in data:
collect_vector(sublist.to_translation()[:])
get_all(data)
return [locations]
def matrices_to_vfield(data):
if isinstance(data, Matrix):
data = deepcopy(data)
return SvMatrixVectorField(data)
if isinstance(data, (list, tuple)):
return [matrices_to_vfield(item) for item in data]
raise TypeError("Unexpected data type from Matrix socket: %s" % type(data))
def vertices_to_vfield(data):
if isinstance(data, (tuple, list)) and len(data) == 3 and isinstance(data[0], (float, int)):
data = deepcopy(data)
return SvConstantVectorField(data)
elif isinstance(data, (list, tuple)):
return [vertices_to_vfield(item) for item in data]
else:
raise TypeError("Unexpected data type from Vertex socket: %s" % type(data))
def numbers_to_sfield(data):
if isinstance(data, (int, float)):
return SvConstantScalarField(data)
elif isinstance(data, (list, tuple)):
return [numbers_to_sfield(item) for item in data]
else:
raise TypeError("Unexpected data type from String socket: %s" % type(data))
def vectors_to_matrices(socket, source_data):
# this means we're going to get a flat list of the incoming
# locations and convert those into matrices proper.
out = get_matrices_from_locs(source_data)
socket.num_matrices = len(out)
return out
def matrices_to_vectors(socket, source_data):
return get_locs_from_matrices(source_data)
def quaternions_to_matrices(socket, source_data):
out = get_matrices_from_quaternions(source_data)
socket.num_matrices = len(out)
return out
def matrices_to_quaternions(socket, source_data):
return get_quaternions_from_matrices(source_data)
def string_to_vector(socket, source_data):
def string_to_vector(source_data):
# it can be so that socket is string but data their are already vectors, performance-wise we check only first item
if isinstance(source_data[0][0], (float, int)):
return [[(v, v, v) for v in obj] for obj in source_data]
return source_data
def string_to_color(socket, source_data):
def string_to_color(source_data):
# it can be so that socket is string but data their are already colors, performance-wise we check only first item
if isinstance(source_data[0][0], (float, int)):
return [[(v, v, v, 1) for v in obj] for obj in source_data]
if len(source_data[0][0]) == 3:
return vector_to_color(socket, source_data)
return vector_to_color(source_data)
return source_data
def vector_to_color(socket, source_data):
def vector_to_color(source_data):
return [[(v[0], v[1], v[2], 1) for v in obj] for obj in source_data]
class ImplicitConversionProhibited(Exception):
def __init__(self, socket, msg=None):
super().__init__()
self.socket = socket
self.node = socket.node
self.from_socket_type = socket.other.bl_idname
self.to_socket_type = socket.bl_idname
if msg is None:
self.message = "Implicit conversion from socket type {} to socket type {} is not supported for socket {} of node {}. Please use explicit conversion nodes.".format(self.from_socket_type, self.to_socket_type, socket.name, socket.node.name)
else:
self.message = msg
def __str__(self):
return self.message
class NoImplicitConversionPolicy():
class NoImplicitConversionPolicy:
"""
Base (empty) implicit conversion policy.
This prohibits any implicit conversions.
......@@ -204,7 +149,7 @@ class NoImplicitConversionPolicy():
raise ImplicitConversionProhibited(socket)
class LenientImplicitConversionPolicy():
class LenientImplicitConversionPolicy:
"""
Lenient implicit conversion policy.
Does not actually convert anything, but passes any
......@@ -222,73 +167,79 @@ class DefaultImplicitConversionPolicy(NoImplicitConversionPolicy):
"""
Default implicit conversion policy.
"""
tests = [
['v', 'm', vectors_to_matrices],
['m', 'v', matrices_to_vectors],
['q', 'm', quaternions_to_matrices],
['m', 'q', matrices_to_quaternions],
['s', 'v', string_to_vector],
['s', 'c', string_to_color],
['v', 'c', vector_to_color]]
default_conversions = {
('SvVerticesSocket', 'SvMatrixSocket'): vectors_to_matrices,
('SvVerticesSocket', 'SvColorSocket'): vector_to_color,
('SvMatrixSocket', 'SvVerticesSocket'): matrices_to_vectors,
('SvMatrixSocket', 'SvQuaternionSocket'): matrices_to_quaternions,
('SvQuaternionSocket', 'SvMatrixSocket'): quaternions_to_matrices,
('SvStringsSocket', 'SvVerticesSocket'): string_to_vector,
('SvStringsSocket', 'SvColorSocket'): string_to_color,
}
@classmethod
def convert(cls, socket, other, source_data):
# socket types that are allowed to consume arbitrary data type.
lenient_socket_types = {
'SvStringsSocket',
'SvObjectSocket',
'SvColorSocket',
'SvVerticesSocket',
}
for t in cls.tests:
if test_socket_type(socket, other, t[0], t[1]):
return t[2](socket, source_data)
if socket.bl_idname in cls.get_lenient_socket_types():
return source_data
if cls.data_type_check(socket, other, source_data):
return source_data
@classmethod
def convert(cls, to_sock, from_sock, source_data):
return super().convert(socket, other, source_data)
# apply default conversion
convert_pattern = (from_sock.bl_idname, to_sock.bl_idname)
if conversion := cls.default_conversions.get(convert_pattern):
return conversion(source_data)
@classmethod
def get_lenient_socket_types(cls):
"""
Return collection of bl_idnames of socket classes
that are allowed to consume arbitrary data type.
"""
return ['SvStringsSocket', 'SvObjectSocket', 'SvColorSocket', 'SvVerticesSocket']
# conversion is not needed
elif to_sock.bl_idname in cls.lenient_socket_types \
or cls.is_expected_type_from_string_socket(to_sock, from_sock, source_data):
return source_data
@classmethod
def get_data_type_checking_socket_types(cls):
return {'SvScalarFieldSocket': SvScalarField,
'SvVectorFieldSocket': SvVectorField,
'SvSurfaceSocket': SvSurface,
'SvCurveSocket': SvCurve}
# raise exception
super().convert(to_sock, from_sock, source_data)
@classmethod
def get_arbitrary_type_socket_types(cls):
return ['SvStringsSocket']
expected_data_types = {
'SvScalarFieldSocket': SvScalarField,
'SvVectorFieldSocket': SvVectorField,
'SvSurfaceSocket': SvSurface,
'SvCurveSocket': SvCurve,
}
@classmethod
def data_type_check(cls, socket, other, source_data):
checking_sockets = cls.get_data_type_checking_socket_types()
expected_type = checking_sockets.get(socket.bl_idname)
if expected_type is None:
def is_expected_type_from_string_socket(cls, to_sock, from_sock, source_data):
if from_sock.bl_idname != 'SvStringsSocket':
return False
if not other.bl_idname in cls.get_arbitrary_type_socket_types():
return False
return is_ultimately(source_data, expected_type)
if expected_type := cls.expected_data_types.get(to_sock.bl_idname):
return is_ultimately(source_data, expected_type)
class FieldImplicitConversionPolicy(DefaultImplicitConversionPolicy):
@classmethod
def convert(cls, socket, other, source_data):
# let policy to decide if deep copy of data is needed
if test_socket_type(socket, other, 'm', 'vf'):
return matrices_to_vfield(source_data)
if test_socket_type(socket, other, 'v', 'vf'):
return vertices_to_vfield(source_data)
if test_socket_type(socket, other, 's', 'sf'):
@staticmethod
def check_nesting_level(func):
def the_check(source_data):
level = get_data_nesting_level(source_data)
if level > 2:
raise TypeError("Too high data nesting level for Number -> Scalar Field conversion: %s" % level)
return numbers_to_sfield(source_data)
return func(source_data)
return the_check
return super().convert(socket, other, source_data)
default_conversions = {
('SvMatrixSocket', 'SvVectorFieldSocket'): matrices_to_vfield,
('SvVerticesSocket', 'SvVectorFieldSocket'): vertices_to_vfield,
('SvStringsSocket', 'SvScalarFieldSocket'): check_nesting_level(numbers_to_sfield),
}
@classmethod
def convert(cls, to_sock, from_sock, source_data):
# let policy to decide if deep copy of data is needed
types_pattern = (from_sock.bl_idname, to_sock.bl_idname)
if conversion := cls.default_conversions.get(types_pattern):
return conversion(source_data)
return super().convert(to_sock, from_sock, source_data)
class SolidImplicitConversionPolicy(NoImplicitConversionPolicy):
......@@ -297,7 +248,17 @@ class SolidImplicitConversionPolicy(NoImplicitConversionPolicy):
try:
return to_solid_recursive(source_data)
except TypeError as e:
raise ImplicitConversionProhibited(socket, msg=f"Cannot perform implicit socket conversion for socket {socket.name}: {e}")
raise ImplicitConversionProhibited(
socket, msg=f"Cannot perform implicit socket conversion for"
f" socket {socket.name}: {e}")
conversions = {
'DefaultImplicitConversionPolicy': DefaultImplicitConversionPolicy,
'FieldImplicitConversionPolicy': FieldImplicitConversionPolicy,
'LenientImplicitConversionPolicy': LenientImplicitConversionPolicy,
'SolidImplicitConversionPolicy': SolidImplicitConversionPolicy,
}
class ConversionPolicies(Enum):
......@@ -314,11 +275,3 @@ class ConversionPolicies(Enum):
@property
def conversion_name(self):
return self.value.__name__
@classmethod
def get_conversion(cls, conversion_name: str):
for enum in cls:
if conversion_name == enum.conversion_name:
return enum.conversion
raise LookupError(f"Conversion policy with name={conversion_name} was not found,"
f"Available names: {[e.conversion_name for e in cls]}")
......@@ -57,3 +57,21 @@ class SvNotFullyConnected(SvProcessingError):
def __str__(self):
return self.message
class ImplicitConversionProhibited(Exception):
def __init__(self, socket, msg=None):
super().__init__()
self.socket = socket
self.node = socket.node
self.from_socket_type = socket.other.bl_idname
self.to_socket_type = socket.bl_idname
if msg is None:
msg = f"Implicit conversion from socket type {self.from_socket_type}" \
f" to socket type {self.to_socket_type} is not supported for" \
f" socket {socket.name} of node {socket.node.name}. Please" \
f" use explicit conversion nodes."
self.message = msg
def __str__(self):
return self.message
......@@ -9,7 +9,7 @@ from bpy.types import Node, NodeSocket, NodeTree, NodeLink
import sverchok.core.events as ev
import sverchok.core.tasks as ts
from sverchok.core.sv_custom_exceptions import CancelError
from sverchok.core.socket_conversions import ConversionPolicies
from sverchok.core.socket_conversions import conversions
from sverchok.utils.profile import profile
from sverchok.utils.logging import log_error
from sverchok.utils.tree_walk import bfs_walk
......@@ -578,7 +578,7 @@ def prepare_input_data(prev_socks: list[Optional[NodeSocket]],
# cast data
if ps.bl_idname != ns.bl_idname:
implicit_conversion = ConversionPolicies.get_conversion(ns.default_conversion_name)
implicit_conversion = conversions[ns.default_conversion_name]
data = implicit_conversion.convert(ns, ps, data)
ns.sv_set(data)
......
from sverchok.core.update_system import prepare_input_data
from mathutils import Matrix
from sverchok.core.socket_conversions import ImplicitConversionProhibited
from sverchok.core.sv_custom_exceptions import ImplicitConversionProhibited
from sverchok.utils.testing import *
from sverchok.utils.logging import debug, info, error
......
......@@ -10,7 +10,6 @@ import sys
import bpy
from mathutils import Matrix, Vector
from sverchok.core.socket_conversions import is_matrix
from sverchok.utils.modules import geom_utils
from sverchok.utils.sv_operator_mixins import SvGenericNodeLocator
......
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать