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

Import Quaternion nodes from 2.79 to 2.80

владелец 858f0f51
Quaternion In
-------------
Quaternion In node constructs quaternions based on various input components provided for a selected mode.
Modes
=====
The available **Modes** are: WXYZ, SCALAR-VECTOR, EULER, AXIS-ANGLE & MATRIX.
+===============+================================================================+
| Mode | Description |
+===============+================================================================+
| WXYZ | Converts W, X, Y, Z components into a quaternion. [1] |
+---------------+----------------------------------------------------------------+
| SCALAR-VECTOR | Converts Scalar & Vector components into a quaternion. [1] |
+---------------+----------------------------------------------------------------+
| EULER | Converts X, Y, Z Euler angles and an order of rotation |
| | into a quaternion. [2,3] |
+---------------+----------------------------------------------------------------+
| AXIS-ANGLE | Converts an Axis & an Angle of rotation into a quaternion. [2] |
+---------------+----------------------------------------------------------------+
| MATRIX | Converts an orthogonal 4x4 rotation matrix into a quaternion. |
+===============+================================================================+
Notes:
[1] : For WXYZ and SCALAR-VECTOR modes the node provides a "Normalize" option to generate a normalized quaternion. All the other modes automatically generate a normalized quaternion.
[2] : For EULER and AXIS-ANGLE modes (which take angle input) the node provides an
angle unit conversion to let the angle values be converted to Radians, Degrees or Unities (0-1 range).
[3] : For EULER mode the node provides the option to select the Euler rotation order:
"XYZ", "XZY", "YXZ", "YZX", "ZXY" or "ZYX".
The modes WXYZ and SCALAR-VECTOR are the same except the WXYZ provides 4 floats (W, X, Y and Z) to generate the quaternion, while SCALAR-VECTOR provides a scalar (W) and a vector (X, Y, Z).
Inputs
======
The node takes a list of various components, based on the selected mode, and it
constructs the corresponding quaternions. The node is vectorized so the inputs take
a value or a list of values. When multiple lists are connected the node will
extend the length of the connected input lists to match the longest one before computing the list of output quaternions.
Based on the selected **Mode** the node makes available the corresponding input sockets:
+===============+==================================+
| Mode | Input Sockets (types) |
+===============+==================================+
| WXYZ | W, X, Y, Z (floats) |
+---------------+----------------------------------+
| SCALAR-VECTOR | Scalar (float) & Vector (vector) |
+---------------+----------------------------------+
| EULER | X, Y, Z angles (floats) |
+---------------+----------------------------------+
| AXIS-ANGLE | Axis (vector) & Angle (float) |
+---------------+----------------------------------+
| MATRIX | Matrix (4x4 matrix) |
+===============+==================================+
Outputs
=======
**Quaternions**
The node outputs a list of one ore more quaternions based on the given input.
The node only generates the quaternions when the output socket is connected.
**********
Quaternion
**********
.. toctree::
:maxdepth: 2
quaternion_in
quaternion_out
quaternion_math
Quaternion Math Node
--------------------
The Quaternion Math node performs various artithmetic operations on quaternions.
The available arithmetic operations and their corresponding inputs/outputs are:
+============+========+========+=====================================+
| Operation | Input | Output | Description |
+============+========+========+=====================================+
| ADD | NQ | Q | Add multiple quaternions |
| SUB | QQ | Q | Subtract two quaternions |
| MULTIPLY | NQ | Q | Multiply multiple quaternions |
| DIVIDE | QQ | Q | Divide two quaternions |
| ROTATE | QQ | Q | Rotate a quaternion around another |
| DOT | QQ | S | Dot product two quaternions |
| DISTANCE | QQ | S | Distance between two quaternions |
| NEGATE | Q | Q | Negate a quaternion |
| CONJUGATE | Q | Q | Conjugate a quaternion |
| INVERT | Q | Q | Invert a quaternion |
| NORMALIZE | Q | Q | Normalize a quaternion |
| SCALE | QS | Q | Scale a quaternion by given factor |
| QUADRANCE | Q | S | Quadrance of a quaternion |
| MAGNITUDE | Q | S | Magnitude of a quaternion |
+============+========+========+=====================================+
where:
NQ = arbitrary number of quaternion inputs
QQ = two quaternion inputs
Q = one quaternion input
QS = one quaternion + scalar value
S = scalar value
For the operations that take multiple quaternion inputs (NQ & QQ) the node provides a PRE / POST option, which lets the node execute the operation on the quaternion inputs in a direct or reverse order. The exceptions to this rule are the ADD, DOT and DISTANCE operations for which the order of quaternions is irrelevant.
For quaternion inputs A and B:
PRE = A op B
POST = B op A
Inputs
======
The input to the node are lists of quaternions as well as control parameters (like scale etc). For certain operations the node takes arbitrary number of quaternion input lists, for others it takes only two quaternion input lists and for some only one quaternion input list.
The inputs accept single value quaternions or a list of quaternions. The node is vectorized so it will extend the quaternion lists to match the longest input.
Operations
==========
* ADD : adds the components of two or more quaternions
q1 = (w1, x1, y1, z1)
q2 = (w2, x2, y2, z2)
q1 + q2 = (w1 + w2, x1 + x2, y1 + y2, z1 + z1)
* SUB : subtracts the components of two quaternions
q1 = (w1, x1, y1, z1)
q2 = (w2, x2, y2, z2)
q1 - q2 = (w1 - w2, x1 - x2, y1 - y2, z1 - z2)
* MULTIPLY : multiplies two or more quaternions
q1 = (w1, x1, y1, z1) = (w1, V1), where V1 = (x1, y1, z1)
q2 = (w2, x2, y2, z2) = (w2, V2), where V2 = (x2, y2, z2)
q1 x q2 = (w1 * w2 - V1 * V2, w1 * V1 + w2 * V2 + V1 x V2)
where V1 * V2 is dot product of vectors V1 & V2
and V1 x V2 is the cross product of vectors V1 & V2
* DIVIDE : divide two quaternions (multiply one quaternion with inverse of the other)
q1 = (w1, x1, y1, z1)
q2 = (w2, x2, y2, z2)
q1 / q2 = q1 x inverse(q2)
* ROTATE : rotates one quaternion around the other quaternion
* DOT : the dot product of two quaternions
q1 = (w1, x1, y1, z1)
q2 = (w2, x2, y2, z2)
q1 * q2 = w1 * w2 + x1 * x2 + y1 * y2 + z1 * z2
* DISTANCE : the distance between two quaternions
q1 = (w1, x1, y1, z1)
q2 = (w2, x2, y2, z2)
Distance(q1, q2) = Magnitude(q1 - q2)
* NEGATE : negates a quaternion
q = (w, x, y, z)
Negate(q) = (-w, -x, -y, -z)
* CONJUGATE : conjugates a quaternion
q = (w, x, y, z)
Conjugate(q) = (w, -x, -y, -z)
* INVERT : inverts a quaternion
q = (w, x, y, z)
Inverse(q) = Conjugate(q) / Magnitude(q)^2
* NORMALIZE : normalizes a quaternion
q = (w, x, y, z)
Normalize(q) = (w/m, x/m, y/m, z/m)
where m = Magnitude(q)
* SCALE : scales the components of a quaternion
q = (w, x, y, z)
s - (float) the scale factor
sf = (sw, sx, sy, sz) - (array of bools) filters which component is scaled
S = (s if sw else 1, s if sx else 1, s if sy else 1, s if sz else 1)
scale(q, S) = (w * Sw, x * Sx, y * Sy, z * Sz)
* QUADRANCE : the quadreance of a quaternion
q = (w, x, y, z)
Quadrance(q) = w * w + x * x + y * y + z * z
Note: essentially this is the dot product of the quaternion with itself, and also equal to square of the magnitude.
* MAGNITUDE : the magnitude of a quaternion
q = (w, x, y, z)
Magnitude(q) = sqrt(w * w + x * x + y * y + z * z)
Note: this is essentially the square root of the quadrance (the length of the quaternion).
Output
======
**Quaternions** or **Values**
Depending on the operation the output to the node is either a quaternion list or scalar value list.
The node computes the results (quaternions or scalar values) only when the output socket is connected.
Quaternion Out
--------------
Quaternion Out node converts a quaternion into various formats for a selected mode.
Modes
=====
The available **Modes** are: WXYZ, EULER, AXIS-ANGLE & MATRIX.
+===============+================================================================+
| Mode | Description |
+===============+================================================================+
| WXYZ | Converts a quaternion into its W, X, Y, Z components. [1] |
+---------------+----------------------------------------------------------------+
| SCALAR-VECTOR | Converts a quaternion into its Scalar & Vector components. [1] |
+---------------+----------------------------------------------------------------+
| EULER | Converts a quaternion into X, Y, Z angles corresponding |
| | to the Euler rotation given an Euler rotation order. [2,3] |
+---------------+----------------------------------------------------------------+
| AXIS-ANGLE | Converts a quaternion into the Axis & Angle of rotation. [2] |
+---------------+----------------------------------------------------------------+
| MATRIX | Converts a quaternion into an orthogonal 4x4 rotation matrix...|
+===============+================================================================+
Notes:
[1] : For WXYZ and SCALAR-VECTOR modes the node provides a "Normalize" option to normalize the input quaternion before outputting its components. All the other modes automatically normalize the quaternion.
[2] : For EULER and AXIS-ANGLE modes, which output angles, the node provides an
angle unit conversion to let the angle output values be converted to Radians,
Degrees or Unities (0-1 range).
[3] : For EULER mode the node provides the option to select the Euler rotation order:
"XYZ", "XZY", "YXZ", "YZX", "ZXY" or "ZYX".
Inputs
======
**Quaternions**
The node takes a list of (one or more) quaternions and based on the selected mode
it converts the quaternions into the corresponding components.
Outputs
=======
Based on the selected **Mode** the node makes available the corresponding output sockets:
+===============+==================================+
| Mode | Output Sockets (types) |
+===============+==================================+
| WXYZ | W, X, Y, Z (floats) |
+---------------+----------------------------------+
| SCALAR-VECTOR | Scalar (float) & Vector (vector) |
+---------------+----------------------------------+
| EULER | X, Y, Z angles (floats) |
+---------------+----------------------------------+
| AXIS-ANGLE | Axis (vector) & Angle (float) |
+---------------+----------------------------------+
| MATRIX | Matrix (4x4 matrix) |
+===============+==================================+
The modes WXYZ and SCALAR-VECTOR are the same except the WXYZ mode outputs the components as 4 floats (W, X, Y and Z), while the SCALAR-VECTOR mode outputs the components as a scalar (W) and a vector (XYZ).
The node only generates the conversion when the output sockets are connected.
......@@ -235,7 +235,6 @@
SvLacunarityNode
SvVectorFractal
## Matrix
SvMatrixGenNodeMK2
MatrixOutNode
......@@ -250,6 +249,11 @@
SvMatrixMathNode
MatrixInterpolationNode
## Quaternion
SvQuaternionInNode
SvQuaternionOutNode
SvQuaternionMathNode
## Logic
SvLogicNode
SvSwitchNode
......
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
import bpy
from bpy.props import EnumProperty, FloatProperty, BoolProperty, StringProperty, FloatVectorProperty
from sverchok.node_tree import SverchCustomTreeNode
from sverchok.data_structure import updateNode, match_long_repeat
from mathutils import Quaternion, Matrix, Euler
from math import pi
modeItems = [
("WXYZ", "WXYZ", "Convert components into quaternion", 0),
("SCALARVECTOR", "Scalar Vector", "Convert Scalar & Vector into quaternion", 1),
("EULER", "Euler Angles", "Convert Euler angles into quaternion", 2),
("AXISANGLE", "Axis Angle", "Convert Axis & Angle into quaternion", 3),
("MATRIX", "Matrix", "Convert Rotation Matrix into quaternion", 4),
]
eulerOrderItems = [
('XYZ', "XYZ", "", 0),
('XZY', 'XZY', "", 1),
('YXZ', 'YXZ', "", 2),
('YZX', 'YZX', "", 3),
('ZXY', 'ZXY', "", 4),
('ZYX', 'ZYX', "", 5)
]
angleUnitItems = [
("RAD", "Rad", "Radians", "", 0),
("DEG", "Deg", 'Degrees', "", 1),
("UNI", "Uni", 'Unities', "", 2)
]
angleConversion = {"RAD": 1.0, "DEG": pi / 180.0, "UNI": 2 * pi}
idMat = [[tuple(v) for v in Matrix()]] # identity matrix
input_sockets = {
"WXYZ": ["W", "X", "Y", "Z"],
"SCALARVECTOR": ["Scalar", "Vector"],
"EULER": ["Angle X", "Angle Y", "Angle Z"],
"AXISANGLE": ["Angle", "Axis"],
"MATRIX": ["Matrix"]
}
class SvQuaternionInNode(bpy.types.Node, SverchCustomTreeNode):
"""
Triggers: Quaternions, In
Tooltip: Generate quaternions from various quaternion components
"""
bl_idname = 'SvQuaternionInNode'
bl_label = 'Quaternion In'
sv_icon = 'SV_COMBINE_IN'
def update_mode(self, context):
# hide all input sockets
for k, names in input_sockets.items():
for name in names:
self.inputs[name].hide_safe = True
# show mode specific input sockets
for name in input_sockets[self.mode]:
self.inputs[name].hide_safe = False
updateNode(self, context)
mode : EnumProperty(
name='Mode', description='The input component format of the quaternion',
items=modeItems, default="WXYZ", update=update_mode)
eulerOrder : EnumProperty(
name="Euler Order", description="Order of the Euler rotations",
default="XYZ", items=eulerOrderItems, update=updateNode)
angleUnits : EnumProperty(
name="Angle Units", description="Angle units (radians/degrees/unities)",
default="RAD", items=angleUnitItems, update=updateNode)
component_w : FloatProperty(
name='W', description='W component',
default=0.0, precision=3, update=updateNode)
component_x : FloatProperty(
name='X', description='X component',
default=0.0, precision=3, update=updateNode)
component_y : FloatProperty(
name='Y', description='Y component',
default=0.0, precision=3, update=updateNode)
component_z : FloatProperty(
name='Z', description='Z component',
default=0.0, precision=3, update=updateNode)
scalar : FloatProperty(
name='Scalar', description='Scalar component of the quaternion',
default=0.0, update=updateNode)
vector : FloatVectorProperty(
name='Vector', description='Vector component of the quaternion',
size=3, default=(0.0, 0.0, 0.0), subtype="XYZ", update=updateNode)
angle_x : FloatProperty(
name='Angle X', description='Rotation angle about X axis',
default=0.0, precision=3, update=updateNode)
angle_y : FloatProperty(
name='Angle Y', description='Rotation angle about Y axis',
default=0.0, precision=3, update=updateNode)
angle_z : FloatProperty(
name='Angle Z', description='Rotation angle about Z axis',
default=0.0, precision=3, update=updateNode)
angle : FloatProperty(
name='Angle', description='Rotation angle about the given axis',
default=0.0, update=updateNode)
axis : FloatVectorProperty(
name='Axis', description='Axis of rotation',
size=3, default=(1.0, 0.0, 0.0), subtype="XYZ", update=updateNode)
normalize : BoolProperty(
name='Normalize', description='Normalize the output quaternion',
default=False, update=updateNode)
def sv_init(self, context):
self.inputs.new('SvStringsSocket', "W").prop_name = 'component_w'
self.inputs.new('SvStringsSocket', "X").prop_name = 'component_x'
self.inputs.new('SvStringsSocket', "Y").prop_name = 'component_y'
self.inputs.new('SvStringsSocket', "Z").prop_name = 'component_z'
self.inputs.new('SvStringsSocket', "Scalar").prop_name = 'scalar'
self.inputs.new('SvVerticesSocket', "Vector").prop_name = "vector"
self.inputs.new('SvStringsSocket', "Angle X").prop_name = 'angle_x'
self.inputs.new('SvStringsSocket', "Angle Y").prop_name = 'angle_y'
self.inputs.new('SvStringsSocket', "Angle Z").prop_name = 'angle_z'
self.inputs.new('SvVerticesSocket', "Axis").prop_name = "axis"
self.inputs.new('SvStringsSocket', "Angle").prop_name = 'angle'
self.inputs.new('SvMatrixSocket', "Matrix")
self.outputs.new('SvQuaternionSocket', "Quaternions")
self.update_mode(context)
def draw_buttons(self, context, layout):
layout.prop(self, "mode", expand=False, text="")
if self.mode == "EULER":
col = layout.column(align=True)
col.prop(self, "eulerOrder", text="")
if self.mode in {"EULER", "AXISANGLE"}:
row = layout.row(align=True)
row.prop(self, "angleUnits", expand=True)
if self.mode in {"WXYZ", "SCALARVECTOR"}:
layout.prop(self, "normalize", toggle=True)
def process(self):
if not self.outputs['Quaternions'].is_linked:
return
inputs = self.inputs
quaternionList = []
if self.mode == "WXYZ":
I = [inputs[n].sv_get()[0] for n in "WXYZ"]
params = match_long_repeat(I)
for wxyz in zip(*params):
q = Quaternion(wxyz)
if self.normalize:
q.normalize()
quaternionList.append(q)
elif self.mode == "SCALARVECTOR":
I = [inputs[n].sv_get()[0] for n in ["Scalar", "Vector"]]
params = match_long_repeat(I)
for scalar, vector in zip(*params):
q = Quaternion([scalar, *vector])
if self.normalize:
q.normalize()
quaternionList.append(q)
elif self.mode == "EULER":
I = [inputs["Angle " + n].sv_get()[0] for n in "XYZ"]
params = match_long_repeat(I)
au = angleConversion[self.angleUnits]
for angleX, angleY, angleZ in zip(*params):
euler = Euler((angleX * au, angleY * au, angleZ * au), self.eulerOrder)
q = euler.to_quaternion()
if self.normalize:
q.normalize()
quaternionList.append(q)
elif self.mode == "AXISANGLE":
I = [inputs[n].sv_get()[0] for n in ["Axis", "Angle"]]
params = match_long_repeat(I)
au = angleConversion[self.angleUnits]
for axis, angle in zip(*params):
q = Quaternion(axis, angle * au)
if self.normalize:
q.normalize()
quaternionList.append(q)
elif self.mode == "MATRIX":
input_M = inputs["Matrix"].sv_get(default=idMat)
for m in input_M:
q = Matrix(m).to_quaternion()
if self.normalize:
q.normalize()
quaternionList.append(q)
self.outputs['Quaternions'].sv_set(quaternionList)
def register():
bpy.utils.register_class(SvQuaternionInNode)
def unregister():
bpy.utils.unregister_class(SvQuaternionInNode)
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
import bpy
from bpy.props import (IntProperty,
FloatProperty,
BoolProperty,
BoolVectorProperty,
EnumProperty)
from sverchok.node_tree import SverchCustomTreeNode
from sverchok.data_structure import updateNode, match_long_repeat
from mathutils import Matrix, Quaternion
from functools import reduce
# list of operations [name: id, input, output, description ]
operations = {
# multiple quaternions => quaternion (NQ or QQ => Q)
"ADD": (10, "NQ", "Q", "Add multiple quaternions"),
"SUB": (11, "QQ", "Q", "Subtract two quaternions"),
"MULTIPLY": (12, "NQ", "Q", "Multiply multiple quaternions"),
"DIVIDE": (13, "QQ", "Q", "Divide two quaternions"),
"ROTATE": (14, "QQ", "Q", "Rotate a quaternion around another"),
# two quaternions => scalar value (QQ => Q)
"DOT": (20, "QQ", "S", "Dot product two quaternions"),
"DISTANCE": (21, "QQ", "S", "Distance between two quaternions"),
# one quaternion => quaternion (Q => Q)
"NEGATE": (30, "Q", "Q", "Negate a quaternion"),
"CONJUGATE": (31, "Q", "Q", "Conjugate a quaternion"),
"INVERT": (32, "Q", "Q", "Invert a quaternion"),
"NORMALIZE": (33, "Q", "Q", "Normalize a quaternion"),
# one quaternion + scalar => quaternion (QS => Q)
"SCALE": (40, "QS", "Q", "Scale a quaternion by given factor"),
# one quaternion => scalar value (Q => S)
"QUADRANCE": (50, "Q", "S", "Quadrance of a quaternion"),
"MAGNITUDE": (51, "Q", "S", "Magnitude of a quaternion"),
}
operationItems = [(k, k.title(), s[3], "", s[0]) for k, s in sorted(operations.items(), key=lambda k: k[1][0])]
# cache various operation categories
NQ_operations = [n for n in operations if operations[n][1] == "NQ"]
QQ_operations = [n for n in operations if operations[n][1] == "QQ"]
Q_operations = [n for n in operations if operations[n][1] in {"Q", "QS"}]
QS_operations = [n for n in operations if operations[n][1] == "QS"]
output_S_operations = [n for n in operations if operations[n][2] == "S"]
prepost_operations = {"SUB", "MULTIPLY", "DIVIDE", "ROTATE"}
prePostItems = [
("PRE", "Pre", "Calculate A op B", 0),
("POST", "Post", "Calculate B op A", 1)
]
id_quat = [Quaternion([1, 0, 0, 0])]
ABC = tuple('ABCDEFGHIJKLMNOPQRSTUVWXYZ')
class SvQuaternionMathNode(bpy.types.Node, SverchCustomTreeNode):
"""
Triggers: Quaternions, Math
Tooltip: Compute various arithmetic operations on quaternions
"""
bl_idname = 'SvQuaternionMathNode'
bl_label = 'Quaternion Math'
bl_icon = 'OUTLINER_OB_EMPTY'
def update_operation(self, context):
self.label = "Quaternion " + self.operation.title()
self.update_sockets()
updateNode(self, context)
prePost : EnumProperty(
name='Pre Post',
description='Order of operations PRE = A op B vs POST = B op A)',
items=prePostItems, default="PRE", update=updateNode)
operation : EnumProperty(
name="Operation",
description="Operation to apply on the given quaternions",
items=operationItems, default="MULTIPLY", update=update_operation)
scale : FloatProperty(
name="Scale",
description="Scale quaternion components by this factor",
default=1.0, update=updateNode)
scales : BoolVectorProperty(
name="Scales", description="Which individual components to scale",
size=4, subtype="QUATERNION",
default=(True, True, True, True), update=updateNode)
def sv_init(self, context):
self.inputs.new('SvStringsSocket', "Scale").prop_name = "scale"
self.inputs.new('SvQuaternionSocket', "A")
self.inputs.new('SvQuaternionSocket', "B")
self.outputs.new('SvQuaternionSocket', "Quaternion")
self.outputs.new('SvStringsSocket', "Value")
self.update_operation(context)
def update(self):
''' Add/remove sockets as A-Z sockets are connected/disconnected '''
# not a multiple quaternion operation ? => no need to update sockets
if self.operation not in NQ_operations:
return
inputs = self.inputs
# get all existing A-Z sockets (connected or not)
inputs_AZ = list(filter(lambda s: s.name in ABC, inputs))
# last A-Z socket connected ? => add an empty A-Z socket at the end
if inputs_AZ and inputs_AZ[-1].links:
name = ABC[len(inputs_AZ)] # pick the next letter A to Z
inputs.new("SvQuaternionSocket", name)
else: # last input disconnected ? => remove all but last unconnected
while len(inputs_AZ) > 2 and not inputs_AZ[-2].links:
s = inputs_AZ[-1]
inputs.remove(s)
inputs_AZ.remove(s)
def update_sockets(self):
''' Upate sockets based on selected operation '''
inputs = self.inputs
if self.operation in Q_operations: # Q or Q+S operations
for a in ABC[1:]: # remove all B-Z inputs (keep A)
if a in inputs:
inputs.remove(inputs[a])
elif self.operation in QQ_operations: # Q + Q operations
for a in ABC[2:]: # remove all C-Z inputs (keep A & B)
if a in inputs:
inputs.remove(inputs[a])
if not "B" in inputs:
inputs.new("SvQuaternionSocket", "B")
else: # multiple Q operations
if not "B" in inputs:
inputs.new("SvQuaternionSocket", "B")
inputs["Scale"].hide_safe = self.operation != "SCALE"
outputs = self.outputs
if self.operation in output_S_operations:
outputs["Quaternion"].hide_safe = True
if outputs["Value"].hide:
outputs["Value"].hide_safe = False
else:
if outputs["Quaternion"].hide:
outputs["Quaternion"].hide_safe = False
outputs["Value"].hide_safe = True
self.update()
def draw_buttons(self, context, layout):
layout.prop(self, "operation", text="")
if self.operation in prepost_operations:
layout.prop(self, "prePost", expand=True)
if self.operation == "SCALE":
row = layout.row(align=True)
row.prop(self, "scales", text="", toggle=True)
def operation_negate(self, q):
qn = Quaternion(q)
qn.negate()
return qn
def operation_rotate(self, q, p):
qr = Quaternion(q)
qr.rotate(p)
return qr
def get_operation(self):
if self.operation == "ADD":
return lambda l: reduce((lambda q, p: q + p), l)
elif self.operation == "SUB":
return lambda q, p: q - p
elif self.operation == "MULTIPLY":
return lambda l: reduce((lambda q, p: q.cross(p)), l)
elif self.operation == "DIVIDE":
return lambda q, p: q.cross(p.inverted())
elif self.operation == "ROTATE":
return self.operation_rotate
elif self.operation == "DOT":
return lambda q, p: q.dot(p)
elif self.operation == "DISTANCE":
return lambda q, p: (p - q).magnitude
elif self.operation == "NEGATE":
return self.operation_negate
elif self.operation == "CONJUGATE":
return lambda q: q.conjugated()
elif self.operation == "INVERT":
return lambda q: q.inverted()
elif self.operation == "NORMALIZE":
return lambda q: q.normalized()
elif self.operation == "SCALE":
return lambda q, s: Quaternion([q[i] * s[i] for i in range(4)])
elif self.operation == "QUADRANCE":
return lambda q: q.dot(q)
elif self.operation == "MAGNITUDE":
return lambda q: q.magnitude
def process(self):
outputs = self.outputs
if not any(s.is_linked for s in outputs):
return
inputs = self.inputs
all_AZ_sockets = list(filter(lambda s: s.name in ABC, inputs))
connected_AZ_sockets = list(filter(lambda s: s.is_linked, all_AZ_sockets))
if len(connected_AZ_sockets) == 0:
return
# collect the quaternion inputs from all connected AZ sockets
I = [s.sv_get(default=id_quat) for s in connected_AZ_sockets]
if self.operation in prepost_operations:
if self.prePost == "POST": # A op B : keep input order
I = I[::-1]
other_sockets = list(filter(lambda s: s.name not in ABC and not s.hide, inputs))
# collect the remaning visible inputs
for socket in other_sockets:
values = socket.sv_get()[0]
if socket.name == "Scale":
qs = []
for s in values:
swxyz = [s if self.scales[i] else 1.0 for i in range(4)]
qs.append(Quaternion(swxyz))
values = qs
I.append(values)
operation = self.get_operation()
if self.operation in NQ_operations:
parameters = match_long_repeat(I)
quaternionList = [operation(params) for params in zip(*parameters)]
elif self.operation in QQ_operations:
parameters = match_long_repeat(I)
quaternionList = [operation(*params) for params in zip(*parameters)]
elif self.operation == "SCALE":
parameters = match_long_repeat(I)
quaternionList = [operation(*params) for params in zip(*parameters)]
else: # single input operations
parameters = I[0] # just quaternion values
quaternionList = [operation(a) for a in parameters]
if self.operation in output_S_operations:
if outputs['Value'].is_linked:
outputs['Value'].sv_set([quaternionList])
else: # output quaternions
if outputs['Quaternion'].is_linked:
outputs['Quaternion'].sv_set(quaternionList)
def register():
bpy.utils.register_class(SvQuaternionMathNode)
def unregister():
bpy.utils.unregister_class(SvQuaternionMathNode)
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
import bpy
from bpy.props import BoolProperty, FloatVectorProperty, EnumProperty
from sverchok.node_tree import SverchCustomTreeNode
from sverchok.data_structure import updateNode
from mathutils import Quaternion
from math import pi
modeItems = [
("WXYZ", "WXYZ", "Convert quaternion into components", 0),
("SCALARVECTOR", "Scalar Vector", "Convert quaternion into Scalar & Vector", 1),
("EULER", "Euler Angles", "Convert quaternion into Euler angles", 2),
("AXISANGLE", "Axis Angle", "Convert quaternion into Axis & Angle", 3),
("MATRIX", "Matrix", "Convert quaternion into Rotation Matrix", 4),
]
eulerOrderItems = [
('XYZ', "XYZ", "", 0),
('XZY', 'XZY', "", 1),
('YXZ', 'YXZ', "", 2),
('YZX', 'YZX', "", 3),
('ZXY', 'ZXY', "", 4),
('ZYX', 'ZYX', "", 5)
]
angleUnitItems = [
("RAD", "Rad", "Radians", "", 0),
("DEG", "Deg", 'Degrees', "", 1),
("UNI", "Uni", 'Unities', "", 2)
]
angleConversion = {"RAD": 1.0, "DEG": 180.0 / pi, "UNI": 0.5 / pi}
output_sockets = {
"WXYZ": ["W", "X", "Y", "Z"],
"SCALARVECTOR": ["Scalar", "Vector"],
"EULER": ["Angle X", "Angle Y", "Angle Z"],
"AXISANGLE": ["Angle", "Axis"],
"MATRIX": ["Matrix"]
}
class SvQuaternionOutNode(bpy.types.Node, SverchCustomTreeNode):
"""
Triggers: Quaternions, Out
Tooltip: Convert quaternions into various quaternion components
"""
bl_idname = 'SvQuaternionOutNode'
bl_label = 'Quaternion Out'
sv_icon = 'SV_COMBINE_OUT'
def update_mode(self, context):
# hide all output sockets
for k, names in output_sockets.items():
for name in names:
self.outputs[name].hide_safe = True
# show mode specific output sockets
for name in output_sockets[self.mode]:
self.outputs[name].hide_safe = False
updateNode(self, context)
mode : EnumProperty(
name='Mode', description='The output component format of the quaternion',
items=modeItems, default="WXYZ", update=update_mode)
eulerOrder : EnumProperty(
name="Euler Order", description="Order of the Euler rotations",
default="XYZ", items=eulerOrderItems, update=updateNode)
quaternion : FloatVectorProperty(
name="Quaternion", description="Quaternion to convert",
size=4, subtype="QUATERNION", default=(0.0, 0.0, 0.0, 0.0),
update=updateNode)
angleUnits : EnumProperty(
name="Angle Units", description="Angle units (radians/degrees/unities)",
default="RAD", items=angleUnitItems, update=updateNode)
normalize : BoolProperty(
name='Normalize', description='Normalize the input quaternion',
default=False, update=updateNode)
def sv_init(self, context):
self.inputs.new('SvQuaternionSocket', "Quaternions").prop_name = "quaternion"
# component outputs
self.outputs.new('SvStringsSocket', "W")
self.outputs.new('SvStringsSocket', "X")
self.outputs.new('SvStringsSocket', "Y")
self.outputs.new('SvStringsSocket', "Z")
# scalar-vector output
self.outputs.new('SvStringsSocket', "Scalar")
self.outputs.new('SvVerticesSocket', "Vector")
# euler angle ouputs
self.outputs.new('SvStringsSocket', "Angle X")
self.outputs.new('SvStringsSocket', "Angle Y")
self.outputs.new('SvStringsSocket', "Angle Z")
# axis-angle output
self.outputs.new('SvVerticesSocket', "Axis")
self.outputs.new('SvStringsSocket', "Angle")
# matrix ouptut
self.outputs.new('SvMatrixSocket', "Matrix")
self.update_mode(context)
def draw_buttons(self, context, layout):
layout.prop(self, "mode", expand=False, text="")
if self.mode == "EULER":
col = layout.column(align=True)
col.prop(self, "eulerOrder", text="")
if self.mode in {"EULER", "AXISANGLE"}:
row = layout.row(align=True)
row.prop(self, "angleUnits", expand=True)
if self.mode in {"WXYZ", "SCALARVECTOR"}:
layout.prop(self, "normalize", toggle=True)
def process(self):
outputs = self.outputs
if not any(s.is_linked for s in outputs):
return
input_Q = self.inputs['Quaternions'].sv_get()
quaternionList = [Quaternion(q) for q in input_Q]
if self.mode == "WXYZ":
if self.normalize:
quaternionList = [q.normalized() for q in quaternionList]
for i, name in enumerate("WXYZ"):
if outputs[name].is_linked:
outputs[name].sv_set([[q[i] for q in quaternionList]])
elif self.mode == "SCALARVECTOR":
if self.normalize:
quaternionList = [q.normalized() for q in quaternionList]
if outputs['Scalar'].is_linked:
scalarList = [q[0] for q in quaternionList]
outputs['Scalar'].sv_set([scalarList])
if outputs['Vector'].is_linked:
vectorList = [tuple(q[1:]) for q in quaternionList]
outputs['Vector'].sv_set([vectorList])
elif self.mode == "EULER":
au = angleConversion[self.angleUnits]
for i, name in enumerate("XYZ"):
if outputs["Angle " + name].is_linked:
angles = [q.to_euler(self.eulerOrder)[i] * au for q in quaternionList]
outputs["Angle " + name].sv_set([angles])
elif self.mode == "AXISANGLE":
if outputs['Axis'].is_linked:
axisList = [tuple(q.axis) for q in quaternionList]
outputs['Axis'].sv_set([axisList])
if outputs['Angle'].is_linked:
au = angleConversion[self.angleUnits]
angleList = [q.angle * au for q in quaternionList]
outputs['Angle'].sv_set([angleList])
elif self.mode == "MATRIX":
if outputs['Matrix'].is_linked:
matrixList = [q.to_matrix().to_4x4() for q in quaternionList]
outputs['Matrix'].sv_set(matrixList)
def register():
bpy.utils.register_class(SvQuaternionOutNode)
def unregister():
bpy.utils.unregister_class(SvQuaternionOutNode)
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать