Motion transfer setup #1

Manually merged
Sybren A. Stüvel merged 18 commits from cgtinker/powership:motion_transfer into main 2023-06-05 12:06:15 +02:00
2 changed files with 106 additions and 142 deletions
Showing only changes of commit a024f31128 - Show all commits

View File

@ -80,7 +80,6 @@ def _on_frame_changed_post(
scene: bpy.types.Scene, depsgraph: bpy.types.Depsgraph scene: bpy.types.Scene, depsgraph: bpy.types.Depsgraph
) -> None: ) -> None:
global _skip_next_autorun global _skip_next_autorun
if not _skip_next_autorun:
_skip_next_autorun = True _skip_next_autorun = True
cgtinker marked this conversation as resolved

Since _skip_next_autorun is a boolean, the if condition can be removed.

Since `_skip_next_autorun` is a boolean, the `if` condition can be removed.
run_node_tree(scene, depsgraph) run_node_tree(scene, depsgraph)

219
nodes.py
View File

@ -3,8 +3,8 @@
import functools import functools
from collections import deque from collections import deque
from copy import copy from copy import copy
from math import degrees, radians, sqrt from math import degrees, radians
from typing import TypeVar, Callable, Optional, Iterable, Any from typing import TypeVar, Callable, Optional, Iterable
import bpy import bpy
import nodeitems_utils import nodeitems_utils
@ -580,25 +580,16 @@ class ToVector(AbstractPowerShipNode):
bl_icon = "EMPTY_ARROWS" bl_icon = "EMPTY_ARROWS"
def init(self, context): def init(self, context):
self.add_optional_input_socket("NodeSocketFloat", "X") self.inputs.new("NodeSocketFloat", "X")
self.add_optional_input_socket("NodeSocketFloat", "Y") self.inputs.new("NodeSocketFloat", "Y")
self.add_optional_input_socket("NodeSocketFloat", "Z") self.inputs.new("NodeSocketFloat", "Z")
self.outputs.new("NodeSocketVector", "Vector") self.outputs.new("NodeSocketVector", "Vector")
def execute(self, depsgraph: bpy.types.Depsgraph) -> None: def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
x = self._get_optional_input_value("X", float) x = self.inputs["X"].default_value or 0.0
y = self._get_optional_input_value("Y", float) y = self.inputs["Y"].default_value or 0.0
z = self._get_optional_input_value("Z", float) z = self.inputs["Z"].default_value or 0.0
self.outputs["Vector"].default_value = Vector((x, y, z))
v = Vector((0.0, 0.0, 0.0))
if x is not None:
v.x = x
if y is not None:
v.y = y
if z is not None:
v.z = z
self.outputs["Vector"].default_value = v
class SplitVector(AbstractPowerShipNode): class SplitVector(AbstractPowerShipNode):
@ -607,13 +598,13 @@ class SplitVector(AbstractPowerShipNode):
bl_icon = "EMPTY_ARROWS" bl_icon = "EMPTY_ARROWS"
def init(self, context): def init(self, context):
self.add_optional_input_socket("NodeSocketVector", "Vector") self.inputs.new("NodeSocketVector", "Vector")
self.outputs.new("NodeSocketFloat", "X") self.outputs.new("NodeSocketFloat", "X")
self.outputs.new("NodeSocketFloat", "Y") self.outputs.new("NodeSocketFloat", "Y")
self.outputs.new("NodeSocketFloat", "Z") self.outputs.new("NodeSocketFloat", "Z")
def execute(self, depsgraph: bpy.types.Depsgraph) -> None: def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
v = self._get_optional_input_value("Vector", Vector) v = self.inputs["Vector"].default_value
self.outputs["X"].default_value = v.x self.outputs["X"].default_value = v.x
self.outputs["Y"].default_value = v.y self.outputs["Y"].default_value = v.y
self.outputs["Z"].default_value = v.z self.outputs["Z"].default_value = v.z
@ -627,13 +618,13 @@ class Distance(AbstractPowerShipNode):
bl_icon = "EMPTY_ARROWS" bl_icon = "EMPTY_ARROWS"
def init(self, context): def init(self, context):
self.add_optional_input_socket("NodeSocketVector", "U") self.inputs.new("NodeSocketVector", "U")
self.add_optional_input_socket("NodeSocketVector", "V") self.inputs.new("NodeSocketVector", "V")
self.outputs.new("NodeSocketFloat", "Float") self.outputs.new("NodeSocketFloat", "Float")
def execute(self, depsgraph: bpy.types.Depsgraph) -> None: def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
u = self._get_optional_input_value("U", Vector) u = self.inputs["U"].default_value
v = self._get_optional_input_value("V", Vector) v = self.inputs["V"].default_value
self.outputs["Float"].default_value = (u - v).length self.outputs["Float"].default_value = (u - v).length
cgtinker marked this conversation as resolved

Does this calculate a normal vector? Or its length?

Does this calculate a normal vector? Or its length?
Review

The length.
I went through the comments and updated them (some were unrelated and happened due to closed eyes copy pasting...)

The length. I went through the comments and updated them (some were unrelated and happened due to closed eyes copy pasting...)
@ -645,55 +636,50 @@ class NormalFromPoints(AbstractPowerShipNode):
bl_icon = "EMPTY_ARROWS" bl_icon = "EMPTY_ARROWS"
def init(self, context): def init(self, context):
self.add_optional_input_socket("NodeSocketVector", "U") self.inputs.new("NodeSocketVector", "U")
self.add_optional_input_socket("NodeSocketVector", "V") self.inputs.new("NodeSocketVector", "V")
self.add_optional_input_socket("NodeSocketVector", "W") self.inputs.new("NodeSocketVector", "W")
self.outputs.new("NodeSocketVector", "Result") self.outputs.new("NodeSocketVector", "Result")
def execute(self, depsgraph: bpy.types.Depsgraph) -> None: def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
u = self._get_optional_input_value("U", Vector) u = self.inputs["U"].default_value
v = self._get_optional_input_value("V", Vector) v = self.inputs["V"].default_value
w = self._get_optional_input_value("W", Vector) w = self.inputs["W"].default_value
a = v - u a = v - u
b = w - u b = w - u
normal = a.cross(b) normal = a.cross(b).normalized()
self.outputs["Result"].default_value = normal self.outputs["Result"].default_value = normal
_enum_up_axis_items = ( _enum_up_axis_items = (
('X', "X", ""), ("X", "X", ""),
('Y', "Y", ""), ("Y", "Y", ""),
('Z', "Z", ""), ("Z", "Z", ""),
) )
_enum_track_axis_items = ( _enum_track_axis_items = (
('X', "X", ""), ("X", "X", ""),
('Y', "Y", ""), ("Y", "Y", ""),
('Z', "Z", ""), ("Z", "Z", ""),
('-X', "-X", ""), ("-X", "-X", ""),
('-Y', "-Y", ""), ("-Y", "-Y", ""),
('-Z', "-Z", ""), ("-Z", "-Z", ""),
) )
class RotateTowards(AbstractPowerShipNode): class RotateTowards(AbstractPowerShipNode):
""" Calculate the rotation from a vector with a track and up axis """Calculate the rotation from a vector with a track and up axis."""
based on the given origin, destination. """
bl_idname = "RotateTowards" bl_idname = "RotateTowards"
bl_label = "Rotate Towards" bl_label = "Rotate Towards"
bl_icon = "EMPTY_ARROWS" bl_icon = "EMPTY_ARROWS"
track: bpy.props.EnumProperty( # type: ignore track: bpy.props.EnumProperty( # type: ignore
name="Track", name="Track", items=_enum_track_axis_items, default="X"
items=_enum_track_axis_items,
default='X'
) )
up: bpy.props.EnumProperty( # type: ignore up: bpy.props.EnumProperty( # type: ignore
name="Up", name="Up", items=_enum_up_axis_items, default="Y"
items=_enum_up_axis_items,
default='Y'
) )
def draw_buttons( def draw_buttons(
@ -704,13 +690,13 @@ class RotateTowards(AbstractPowerShipNode):
layout.prop(self, "track") layout.prop(self, "track")
def init(self, context): def init(self, context):
self.add_optional_input_socket("NodeSocketVector", "Origin") self.inputs.new("NodeSocketVector", "Vector")
self.add_optional_input_socket("NodeSocketVector", "Destination") self.inputs.new("NodeSocketVector", "RotateTo")
self.outputs.new("NodeSocketQuaternion", "Rotation") self.outputs.new("NodeSocketQuaternion", "Rotation")
def execute(self, depsgraph: bpy.types.Depsgraph) -> None: def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
origin = self._get_optional_input_value("Origin", Vector) origin = self.inputs["Vector"].default_value
destination = self._get_optional_input_value("Destination", Vector) destination = self.inputs["RotateTo"].default_value
vec = Vector((destination - origin)) vec = Vector((destination - origin))
rot = vec.to_track_quat(self.track, self.up) rot = vec.to_track_quat(self.track, self.up)
self.outputs["Rotation"].default_value = rot.normalized() self.outputs["Rotation"].default_value = rot.normalized()
@ -722,16 +708,15 @@ class OffsetRotation(AbstractPowerShipNode):
bl_idname = "OffsetRotation" bl_idname = "OffsetRotation"
bl_label = "Offset Rotation" bl_label = "Offset Rotation"
bl_icon = "EMPTY_ARROWS" bl_icon = "EMPTY_ARROWS"
default_value = Quaternion()
def init(self, context): def init(self, context):
self.add_optional_input_socket("NodeSocketQuaternion", "Base") self.inputs.new("NodeSocketQuaternion", "Base")
self.add_optional_input_socket("NodeSocketQuaternion", "Offset") self.inputs.new("NodeSocketQuaternion", "Offset")
self.outputs.new("NodeSocketQuaternion", "Rotation") self.outputs.new("NodeSocketQuaternion", "Rotation")
def execute(self, depsgraph: bpy.types.Depsgraph) -> None: def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
base = self._get_optional_input_value("Base", Quaternion) base = self.inputs["Base"].default_value
offset = self._get_optional_input_value("Offset", Quaternion) offset = self.inputs["Offset"].default_value
self.outputs["Rotation"].default_value = base @ offset self.outputs["Rotation"].default_value = base @ offset
@ -741,19 +726,19 @@ class MapRange(AbstractPowerShipNode):
bl_icon = "EMPTY_ARROWS" bl_icon = "EMPTY_ARROWS"
def init(self, context): def init(self, context):
self.add_optional_input_socket("NodeSocketFloat", "Value") self.inputs.new("NodeSocketFloat", "Value")
self.add_optional_input_socket("NodeSocketFloat", "From Min") self.inputs.new("NodeSocketFloat", "From Min")
self.add_optional_input_socket("NodeSocketFloat", "From Max") self.inputs.new("NodeSocketFloat", "From Max")
self.add_optional_input_socket("NodeSocketFloat", "To Min") self.inputs.new("NodeSocketFloat", "To Min")
self.add_optional_input_socket("NodeSocketFloat", "To Max") self.inputs.new("NodeSocketFloat", "To Max")
self.outputs.new("NodeSocketFloat", "Result") self.outputs.new("NodeSocketFloat", "Result")
def execute(self, depsgraph: bpy.types.Depsgraph) -> None: def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
val = self._get_optional_input_value("Value", float) val = self.inputs["Value"].default_value
fmin = self._get_optional_input_value("From Min", float) fmin = self.inputs["From Min"].default_value
fmax = self._get_optional_input_value("From Max", float) fmax = self.inputs["From Max"].default_value
tmin = self._get_optional_input_value("To Min", float) tmin = self.inputs["To Min"].default_value
tmax = self._get_optional_input_value("To Max", float) tmax = self.inputs["To Max"].default_value
factor = (tmax - tmin) / (fmax - fmin) factor = (tmax - tmin) / (fmax - fmin)
offset = tmin - factor * fmin offset = tmin - factor * fmin
@ -767,46 +752,28 @@ class AngleFromVectors(AbstractPowerShipNode):
bl_label = "Angle From Vectors" bl_label = "Angle From Vectors"
bl_icon = "EMPTY_ARROWS" bl_icon = "EMPTY_ARROWS"
angle_type: bpy.props.EnumProperty( # type: ignore
name="Type",
items=[
("DEFAULT", "Unsigned", ""),
("SIGNED", "Signed", ""),
],
)
def draw_buttons(
self, context: bpy.types.Context, layout: bpy.types.UILayout
) -> None:
super().draw_buttons(context, layout)
layout.prop(self, "angle_type")
def init(self, context): def init(self, context):
self.add_optional_input_socket("NodeSocketVector", "U") self.inputs.new("NodeSocketVector", "U")
self.add_optional_input_socket("NodeSocketVector", "V") self.inputs.new("NodeSocketVector", "V")
self.outputs.new("NodeSocketFloat", "Angle") self.outputs.new("NodeSocketFloat", "Angle")
def execute(self, depsgraph: bpy.types.Depsgraph) -> None: def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
u = self._get_optional_input_value("U", Vector) u = self.inputs["U"].default_value
v = self._get_optional_input_value("V", Vector) v = self.inputs["V"].default_value
signed = self.angle_type == 'SIGNED'
angle = 0 angle = 0
if not (u.length == 0 or v.length == 0): if not (u.length_squared == 0 or v.length_squared == 0):
if signed:
angle = u.angle_signed(v)
else:
angle = u.angle(v) angle = u.angle(v)
self.outputs["Angle"].default_value = degrees(angle) self.outputs["Angle"].default_value = degrees(angle)
_enum_vector_math_operations = [ _enum_vector_math_operations = [
('ADD', "Add", ""), ("ADD", "Add", ""),
('SUBSTRACT', "Substract", ""), ("SUBSTRACT", "Substract", ""),
('MULTIPLY', "Mutliply", ""), ("MULTIPLY", "Mutliply", ""),
('DIVIDE', "Divide", ""), ("DIVIDE", "Divide", ""),
('CROSS', "Cross", ""), ("CROSS", "Cross", ""),
] ]
@ -827,38 +794,36 @@ class VectorMath(AbstractPowerShipNode):
layout.prop(self, "operation") layout.prop(self, "operation")
def init(self, context): def init(self, context):
self.add_optional_input_socket("NodeSocketVector", "U") self.inputs.new("NodeSocketVector", "U")
self.add_optional_input_socket("NodeSocketVector", "V") self.inputs.new("NodeSocketVector", "V")
self.outputs.new("NodeSocketVector", "Result") self.outputs.new("NodeSocketVector", "Result")
def execute(self, depsgraph: bpy.types.Depsgraph) -> None: def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
u = self._get_optional_input_value("U", Vector) u = self.inputs["U"].default_value
v = self._get_optional_input_value("V", Vector) v = self.inputs["V"].default_value
match self.operation: match self.operation:
case 'ADD': case "ADD":
res = u + v res = u + v
case 'MULTIPLY': case "MULTIPLY":
res = u * v res = u * v
case 'SUBSTRACT': case "SUBSTRACT":
res = u - v res = u - v
case 'DIVIDE': case "DIVIDE":
res = Vector([x/y if y != 0.0 else float("nan") res = Vector((x / y if y != 0.0 else 0.0 for x, y in zip(u, v)))
for x, y in zip(u, v)]) case "CROSS":
case 'CROSS':
res = u.cross(v) res = u.cross(v)
case _: case _:
print("Vector math operation not found:", self.operation) raise ValueError(f"Vector math operation not found: {self.operation}\n")
raise ValueError
self.outputs["Result"].default_value = res self.outputs["Result"].default_value = res
_enum_math_operations = [ _enum_math_operations = [
('ADD', "Add", ""), ("ADD", "Add", ""),
('SUBSTRACT', "Substract", ""), ("SUBSTRACT", "Substract", ""),
('MULTIPLY', "Mutliply", ""), ("MULTIPLY", "Mutliply", ""),
('DIVIDE', "Divide", ""), ("DIVIDE", "Divide", ""),
] ]
@ -879,26 +844,25 @@ class Math(AbstractPowerShipNode):
layout.prop(self, "operation") layout.prop(self, "operation")
def init(self, context): def init(self, context):
self.add_optional_input_socket("NodeSocketFloat", "U") self.inputs.new("NodeSocketFloat", "U")
self.add_optional_input_socket("NodeSocketFloat", "V") self.inputs.new("NodeSocketFloat", "V")
self.outputs.new("NodeSocketFloat", "Result") self.outputs.new("NodeSocketFloat", "Result")
def execute(self, depsgraph: bpy.types.Depsgraph) -> None: def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
u = self._get_optional_input_value("U", float) u = self.inputs["U"].default_value
v = self._get_optional_input_value("V", float) v = self.inputs["V"].default_value
match self.operation: match self.operation:
case 'ADD': case "ADD":
res = u + v res = u + v
case 'MULTIPLY': case "MULTIPLY":
res = u * v res = u * v
case 'SUBSTRACT': case "SUBSTRACT":
res = u - v res = u - v
case 'DIVIDE': case "DIVIDE":
res = u/v if v != 0 else float("nan") res = u / v if v != 0 else 0
case _: case _:
print("Math operation not found:", self.operation) raise ValueError(f"Math operation not found: {self.operation}\n")
raise ValueError
self.outputs["Result"].default_value = res self.outputs["Result"].default_value = res
@ -994,12 +958,13 @@ class SetBoneNode(AbstractPowerShipNode):
match self.space: match self.space:
case "WORLD": case "WORLD":
bone_mat_world = Matrix.LocRotScale( bone_mat_world = Matrix.LocRotScale(loc, rot, scale)
loc, rot.normalized(), scale)
loc, rot, scale = bone_mat_world.decompose() loc, rot, scale = bone_mat_world.decompose()
case "CHANNELS": case "CHANNELS":
bone_rest_rot_scale.translation = v_nil bone_rest_rot_scale.translation = v_nil
mat_rot_scale = Matrix.LocRotScale(v_nil, rot, scale) @ bone_rest_rot_scale mat_rot_scale = (
Matrix.LocRotScale(v_nil, rot, scale) @ bone_rest_rot_scale
)
mat_loc = Matrix.Translation(loc) mat_loc = Matrix.Translation(loc)
bone_mat_world = mat_loc @ mat_rot_scale bone_mat_world = mat_loc @ mat_rot_scale
@ -1318,7 +1283,7 @@ node_categories = [
nodeitems_utils.NodeItem("ClampNode"), nodeitems_utils.NodeItem("ClampNode"),
nodeitems_utils.NodeItem("ToEulerNode"), nodeitems_utils.NodeItem("ToEulerNode"),
nodeitems_utils.NodeItem("FromEulerNode"), nodeitems_utils.NodeItem("FromEulerNode"),
] ],
), ),
PowerShipNodeCategory( PowerShipNodeCategory(
"DEBUG", "DEBUG",