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
4 changed files with 547 additions and 30 deletions
Showing only changes of commit 2079b82f30 - Show all commits

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
/.venv
/.vscode
__pycache__ __pycache__
*.DS_Store
*.pyc *.pyc
*.blend1 *.blend1

View File

@ -1,14 +1,14 @@
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
is_first_load = "nodes" not in locals() import bpy
from . import nodes from . import nodes
is_first_load = "nodes" not in locals()
if not is_first_load: if not is_first_load:
import sys import sys
nodes = sys.modules[nodes.__name__] nodes = sys.modules[nodes.__name__]
import bpy
_skip_next_autorun = False _skip_next_autorun = False
@ -50,7 +50,6 @@ def execute_tree(
# The running of this tree will trigger another depsgraph update, which # The running of this tree will trigger another depsgraph update, which
# should not trigger yet another execution. # should not trigger yet another execution.
global _skip_next_autorun global _skip_next_autorun
try: try:
tree.run_event(depsgraph, mode) tree.run_event(depsgraph, mode)
except Exception: except Exception:
@ -67,7 +66,6 @@ def _on_depsgraph_update_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 _skip_next_autorun: if _skip_next_autorun:
_skip_next_autorun = False _skip_next_autorun = False
return return
@ -85,6 +83,7 @@ def _on_depsgraph_update_post(
return return
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.
powership_mode = scene.powership_mode powership_mode = scene.powership_mode
if powership_mode == "AUTO": if powership_mode == "AUTO":
# TODO: don't use the global context here. # TODO: don't use the global context here.
powership_mode = _choose_auto_mode(bpy.context) powership_mode = _choose_auto_mode(bpy.context)
@ -98,6 +97,34 @@ def _choose_auto_mode(context: bpy.types.Context) -> str:
return "FORWARD" return "FORWARD"
@bpy.app.handlers.persistent # type: ignore
def _on_frame_changed_post(
scene: bpy.types.Scene, depsgraph: bpy.types.Depsgraph
) -> None:
global _skip_next_autorun
if _skip_next_autorun:
_skip_next_autorun = True
# updating on frame change to allow
# keyframed animation influencing the rig
for tree in bpy.data.node_groups:
if tree.bl_idname != "PowerShipNodeTree":
continue
if not tree.autorun:
return
powership_mode = scene.powership_mode
if powership_mode == "AUTO":
if bpy.context.object and bpy.context.object.mode == "POSE":
powership_mode = "BACKWARD"
else:
powership_mode = "FORWARD"
execute_tree(tree, depsgraph, powership_mode)
classes = ( classes = (
# Operators: # Operators:
PowerShip_OT_execute_tree, PowerShip_OT_execute_tree,
@ -114,6 +141,7 @@ def register() -> None:
) )
bpy.app.handlers.depsgraph_update_post.append(_on_depsgraph_update_post) bpy.app.handlers.depsgraph_update_post.append(_on_depsgraph_update_post)
bpy.app.handlers.frame_change_post.append(_on_frame_changed_post)
def unregister() -> None: def unregister() -> None:

538
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 from math import degrees, radians, sqrt
from typing import TypeVar, Callable, Optional, Iterable from typing import TypeVar, Callable, Optional, Iterable, Any
import bpy import bpy
import nodeitems_utils import nodeitems_utils
@ -67,7 +67,10 @@ class PowerShipNodeTree(bpy.types.NodeTree):
def _prepare_nodes(self) -> None: def _prepare_nodes(self) -> None:
for node in self.nodes: for node in self.nodes:
node.reset_run() try:
node.reset_run()
except AttributeError:
print(node)
def _run_from_node( def _run_from_node(
self, depsgraph: bpy.types.Depsgraph, start_node: "AbstractPowerShipNode" self, depsgraph: bpy.types.Depsgraph, start_node: "AbstractPowerShipNode"
@ -83,7 +86,8 @@ class PowerShipNodeTree(bpy.types.NodeTree):
nodes_before = list(node.exec_order_prerequisites()) nodes_before = list(node.exec_order_prerequisites())
if nodes_before: if nodes_before:
print(f" {len(nodes_before)} prerequisites, going there first") print(
f" {len(nodes_before)} prerequisites, going there first")
# There are nodes to execute before this one. Push them to the front of the queue. # There are nodes to execute before this one. Push them to the front of the queue.
to_visit.extendleft(reversed(nodes_before)) to_visit.extendleft(reversed(nodes_before))
continue continue
@ -169,10 +173,12 @@ class AbstractPowerShipNode(bpy.types.Node):
self.add_execution_socket_output() self.add_execution_socket_output()
def add_execution_socket_input(self) -> None: def add_execution_socket_input(self) -> None:
self.inputs.new(NodeSocketExecute.bl_idname, NodeSocketExecute.bl_label) self.inputs.new(NodeSocketExecute.bl_idname,
NodeSocketExecute.bl_label)
def add_execution_socket_output(self) -> None: def add_execution_socket_output(self) -> None:
self.outputs.new(NodeSocketExecute.bl_idname, NodeSocketExecute.bl_label) self.outputs.new(NodeSocketExecute.bl_idname,
NodeSocketExecute.bl_label)
def add_optional_input_socket( def add_optional_input_socket(
self, typename: str, label: str self, typename: str, label: str
@ -265,7 +271,8 @@ class AbstractPowerShipNode(bpy.types.Node):
"""Return the connected socket value, or None if not connected.""" """Return the connected socket value, or None if not connected."""
input_socket = self.inputs[input_socket_name] input_socket = self.inputs[input_socket_name]
for link in input_socket.links: for link in input_socket.links:
convertedValue = expectType(link.from_socket.default_value) # type: ignore convertedValue = expectType(
link.from_socket.default_value) # type: ignore
return convertedValue return convertedValue
return None return None
@ -398,7 +405,8 @@ class GetBoneNode(AbstractPowerShipNode):
bl_label = "Get Bone" bl_label = "Get Bone"
bl_icon = "BONE_DATA" bl_icon = "BONE_DATA"
head_tail: bpy.props.EnumProperty(name="Side", items=_bone_head_tail) # type: ignore head_tail: bpy.props.EnumProperty(
name="Side", items=_bone_head_tail) # type: ignore
def draw_buttons( def draw_buttons(
self, context: bpy.types.Context, layout: bpy.types.UILayout self, context: bpy.types.Context, layout: bpy.types.UILayout
@ -440,7 +448,8 @@ class GetBoneNode(AbstractPowerShipNode):
bone_rest_rot_scale = bone.bone.matrix_local.copy() bone_rest_rot_scale = bone.bone.matrix_local.copy()
bone_rest_rot_scale.translation = v_nil bone_rest_rot_scale.translation = v_nil
_, rot, scale = (mat_world @ bone_rest_rot_scale.inverted()).decompose() _, rot, scale = (
mat_world @ bone_rest_rot_scale.inverted()).decompose()
self.outputs["Location"].default_value = arm_matrix @ loc self.outputs["Location"].default_value = arm_matrix @ loc
self.outputs["Rotation"].default_value = rot self.outputs["Rotation"].default_value = rot
@ -536,7 +545,8 @@ class SetControlNode(AbstractPowerShipNode):
def execute(self, depsgraph: bpy.types.Depsgraph) -> None: def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
control_location = self._get_optional_input_value("Location", Vector) control_location = self._get_optional_input_value("Location", Vector)
control_rotation = self._get_optional_input_value("Rotation", Quaternion) control_rotation = self._get_optional_input_value(
"Rotation", Quaternion)
control_scale = self._get_optional_input_value("Scale", Vector) control_scale = self._get_optional_input_value("Scale", Vector)
control_obj = self._get_input_value("Control", bpy.types.Object) control_obj = self._get_input_value("Control", bpy.types.Object)
@ -576,6 +586,450 @@ class SetControlNode(AbstractPowerShipNode):
return Matrix.Identity(4) return Matrix.Identity(4)
class ToVector(AbstractPowerShipNode):
bl_idname = "ToVector"
bl_label = "To Vector"
bl_icon = "EMPTY_ARROWS"
def init(self, context):
self.add_execution_sockets()
self.add_optional_input_socket("NodeSocketFloat", "X")
self.add_optional_input_socket("NodeSocketFloat", "Y")
self.add_optional_input_socket("NodeSocketFloat", "Z")
self.outputs.new("NodeSocketVector", "Vector")
def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
x = self._get_optional_input_value("X", float)
y = self._get_optional_input_value("Y", float)
z = self._get_optional_input_value("Z", float)
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):
bl_idname = "SplitVector"
bl_label = "Split Vector"
bl_icon = "EMPTY_ARROWS"
def init(self, context):
self.add_execution_sockets()
self.add_optional_input_socket("NodeSocketVector", "Vector")
self.outputs.new("NodeSocketFloat", "X")
self.outputs.new("NodeSocketFloat", "Y")
self.outputs.new("NodeSocketFloat", "Z")
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...)
def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
v = self._get_optional_input_value("Vector", Vector)
if not v:
return
self.outputs["X"].default_value = v.x
self.outputs["Y"].default_value = v.y
self.outputs["Z"].default_value = v.z
class Distance(AbstractPowerShipNode):
""" Calculate normal from three points. """
bl_idname = "Distance"
bl_label = "Distance"
bl_icon = "EMPTY_ARROWS"
def init(self, context):
self.add_execution_sockets()
self.add_optional_input_socket("NodeSocketVector", "U")
self.add_optional_input_socket("NodeSocketVector", "V")
self.outputs.new("NodeSocketFloat", "Float")
def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
u = self._get_optional_input_value("U", Vector)
v = self._get_optional_input_value("V", Vector)
if not (u and v):
return
self.outputs["Float"].default_value = (u-v).length
class NormalFromPoints(AbstractPowerShipNode):
""" Calculate normal from three points. """
bl_idname = "NormalFromPoints"
bl_label = "Normal from Points"
bl_icon = "EMPTY_ARROWS"
def init(self, context):
self.add_execution_sockets()
self.add_optional_input_socket("NodeSocketVector", "U")
self.add_optional_input_socket("NodeSocketVector", "V")
self.add_optional_input_socket("NodeSocketVector", "W")
self.outputs.new("NodeSocketVector", "Result")
def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
u = self._get_optional_input_value("U", Vector)
v = self._get_optional_input_value("V", Vector)
w = self._get_optional_input_value("W", Vector)
if not (u and v and w):
return
a = v - u
b = w - u
normal = a.cross(b)
self.outputs["Result"].default_value = normal
class UpAxisSocket(bpy.types.NodeSocket):
'''Custom node socket type'''
bl_idname = 'UpAxisSocket'
bl_label = "Up Axis Socket"
link_limit = 0
# Enum items list
axis_items = (
('X', "X", ""),
('Y', "Y", ""),
('Z', "Z", ""),
)
# default_value = "UP"
default_value: bpy.props.EnumProperty(
name="Axis",
description="",
items=axis_items,
default='X',
)
# Optional function for drawing the socket input value
def draw(self, context, layout, node, text):
if self.is_output or self.is_linked:
layout.label(text=text)
else:
layout.prop(self, "default_value", text=text)
def draw_color(self, context, node):
return (1.0, 0.4, 0.216, 0.5)
class TrackAxisSocket(bpy.types.NodeSocket):
'''Custom node socket type'''
bl_idname = 'TrackAxisSocket'
bl_label = "Track Axis Socket"
link_limit = 0
# Enum items list
axis_items = (
('X', "X", ""),
('Y', "Y", ""),
('Z', "Z", ""),
('-X', "-X", ""),
('-Y', "-Y", ""),
('-Z', "-Z", ""),
)
# default_value = "UP"
default_value: bpy.props.EnumProperty(
name="Axis",
description="",
items=axis_items,
default='Y',
)
# Optional function for drawing the socket input value
def draw(self, context, layout, node, text):
if self.is_output or self.is_linked:
layout.label(text=text)
else:
layout.prop(self, "default_value", text=text)
# Socket color
def draw_color(self, context, node):
return (1.0, 0.4, 0.216, 0.5)
class RotateTowards(AbstractPowerShipNode):
""" Calculate rotation from tangent (left / right), normal (forward) and binormal (up). """
bl_idname = "RotateTowards"
bl_label = "Rotate Towards"
bl_icon = "EMPTY_ARROWS"
def init(self, context):
self.add_execution_sockets()
self.add_optional_input_socket("UpAxisSocket", "Up")
self.add_optional_input_socket("TrackAxisSocket", "Track")
self.add_optional_input_socket("NodeSocketVector", "Origin")
self.add_optional_input_socket("NodeSocketVector", "Destination")
self.outputs.new("NodeSocketQuaternion", "Rotation")
def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
origin = self._get_optional_input_value("Origin", Vector)
destination = self._get_optional_input_value("Destination", Vector)
track = self._get_input_value("Track", str)
up = self._get_input_value("Up", str)
if not (origin and destination):
return
# Set the rotation of the control
vec = Vector((destination - origin))
vec.normalize()
rot = vec.to_track_quat(track, up)
self.outputs["Rotation"].default_value = rot
class OffsetRotation(AbstractPowerShipNode):
""" Calculate rotation from tangent (left / right), normal (forward) and binormal (up). """
bl_idname = "OffsetRotation"
bl_label = "Offset Rotation"
bl_icon = "EMPTY_ARROWS"
def init(self, context):
self.add_execution_sockets()
self.add_optional_input_socket("NodeSocketQuaternion", "Base")
self.add_optional_input_socket("NodeSocketQuaternion", "Offset")
self.outputs.new("NodeSocketQuaternion", "Rotation")
def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
base = self._get_optional_input_value("Base", Quaternion)
offset = self._get_optional_input_value("Offset", Quaternion)
if not (base and offset):
return
self.outputs["Rotation"].default_value = base @ offset
class MapRange(AbstractPowerShipNode):
"""Sets the location and/or rotation of the 3D cursor"""
bl_idname = "MapRange"
bl_label = "Map Range"
bl_icon = "EMPTY_ARROWS"
def init(self, context):
self.add_execution_sockets()
self.add_optional_input_socket("NodeSocketFloat", "Value")
self.add_optional_input_socket("NodeSocketFloat", "From Min")
self.add_optional_input_socket("NodeSocketFloat", "From Max")
self.add_optional_input_socket("NodeSocketFloat", "To Min")
self.add_optional_input_socket("NodeSocketFloat", "To Max")
self.outputs.new("NodeSocketFloat", "Result")
def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
val = self._get_optional_input_value("Value", float)
fmin = self._get_optional_input_value("Value", float)
fmax = self._get_optional_input_value("Value", float)
tmin = self._get_optional_input_value("Value", float)
tmax = self._get_optional_input_value("Value", float)
if not (val and fmin and fmax and tmin and tmax):
return
slope = (tmax - tmin) / (fmax - fmin)
offset = tmin - slope * fmin
self.outputs["Result"].default_value = slope * val + offset
class AngleTypeSocket(bpy.types.NodeSocket):
bl_idname = 'AngleTypeSocket'
bl_label = "Angle Type"
link_limit = 0
# Enum items list
ops_items = (
('SIGNED', "Signed", ""),
('UNSIGNED', "Default", ""),
)
# default_value = "UP"
default_value: bpy.props.EnumProperty(
name="Operation",
description="",
items=ops_items,
default='UNSIGNED',
)
# Optional function for drawing the socket input value
def draw(self, context, layout, node, text):
if self.is_output or self.is_linked:
layout.label(text=text)
else:
layout.prop(self, "default_value", text=text)
# Socket color
def draw_color(self, context, node):
return (1.0, 0.4, 0.216, 0.5)
class RotationFromAngle(AbstractPowerShipNode):
bl_idname = "RotationFromAngle"
bl_label = "Rotation From Vector Angle"
bl_icon = "EMPTY_ARROWS"
def init(self, context):
self.add_execution_sockets()
self.add_optional_input_socket("UpAxisSocket", "Axis")
self.add_optional_input_socket("AngleTypeSocket", "Type")
self.add_optional_input_socket("NodeSocketVector", "U")
self.add_optional_input_socket("NodeSocketVector", "V")
self.outputs.new("NodeSocketQuaternion", "Rotation")
def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
u = self._get_optional_input_value("U", Vector)
v = self._get_optional_input_value("V", Vector)
axis = self._get_input_value("Axis", str)
angle_type = self._get_input_value("Type", str)
signed = angle_type == 'SIGNED'
if not (u and v):
return
angle = 0
if not (u.length == 0 or v.length == 0):
if signed:
angle = u.angle_signed(v)
else:
angle = u.angle(v)
m = Matrix.Rotation(angle, 3, axis)
res = m.to_quaternion()
self.outputs["Rotation"].default_value = res
class MathOperationSocket(bpy.types.NodeSocket):
bl_idname = 'MathOperationSocket'
bl_label = "Operations"
link_limit = 0
# Enum items list
ops_items = (
('ADD', "Add", ""),
('SUBSTRACT', "Substract", ""),
('MULTIPLY', "Mutliply", ""),
('DIVIDE', "Divide", ""),
('CROSS', "Cross", ""),
)
# default_value = "UP"
default_value: bpy.props.EnumProperty(
name="Operation",
description="",
items=ops_items,
default='ADD',
)
# Optional function for drawing the socket input value
def draw(self, context, layout, node, text):
if self.is_output or self.is_linked:
layout.label(text=text)
else:
layout.prop(self, "default_value", text=text)
# Socket color
def draw_color(self, context, node):
return (1.0, 0.4, 0.216, 0.5)
class VectorMath(AbstractPowerShipNode):
"""Sets the location and/or rotation of the 3D cursor"""
bl_idname = "VectorMath"
bl_label = "Vector Math"
bl_icon = "EMPTY_ARROWS"
def init(self, context):
self.add_execution_sockets()
self.add_optional_input_socket("MathOperationSocket", "Operation")
self.add_optional_input_socket("NodeSocketVector", "U")
self.add_optional_input_socket("NodeSocketVector", "V")
self.outputs.new("NodeSocketVector", "Result")
def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
u = self._get_optional_input_value("U", Vector)
v = self._get_optional_input_value("V", Vector)
mode = self._get_input_value("Operation", str)
if not (u and v):
return
match mode:
case 'ADD':
res = u+v
case 'MULTIPLY':
res = u*v
case 'SUBSTRACT':
res = u-v
case 'DIVIDE':
res = Vector([x/y for x, y in zip(u, v)])
case 'CROSS':
res = u.cross(v)
case _:
raise ValueError
self.outputs["Result"].default_value = res
class Math(AbstractPowerShipNode):
"""Sets the location and/or rotation of the 3D cursor"""
bl_idname = "Math"
bl_label = "Math"
bl_icon = "EMPTY_ARROWS"
def init(self, context):
self.add_execution_sockets()
self.add_optional_input_socket("MathOperationSocket", "Operation")
self.add_optional_input_socket("NodeSocketFloat", "U")
self.add_optional_input_socket("NodeSocketFloat", "V")
self.outputs.new("NodeSocketFloat", "Result")
def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
u = self._get_optional_input_value("U", float)
v = self._get_optional_input_value("V", float)
mode = self._get_input_value("Operation", str)
if not (u and v):
return
match mode:
case 'ADD':
res = u+v
case 'MULTIPLY':
res = u*v
case 'SUBSTRACT':
res = u-v
case 'DIVIDE':
res = u/v
case _:
print("MODE NOT FOUND:", mode)
raise ValueError
self.outputs["Result"].default_value = res
class SetCursorNode(AbstractAlwaysExecuteNode): class SetCursorNode(AbstractAlwaysExecuteNode):
"""Sets the location and/or rotation of the 3D cursor""" """Sets the location and/or rotation of the 3D cursor"""
@ -590,7 +1044,8 @@ class SetCursorNode(AbstractAlwaysExecuteNode):
def execute(self, depsgraph: bpy.types.Depsgraph) -> None: def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
control_location = self._get_optional_input_value("Location", Vector) control_location = self._get_optional_input_value("Location", Vector)
control_rotation = self._get_optional_input_value("Rotation", Quaternion) control_rotation = self._get_optional_input_value(
"Rotation", Quaternion)
cursor = depsgraph.scene.cursor cursor = depsgraph.scene.cursor
if control_location is not None: if control_location is not None:
@ -618,7 +1073,8 @@ class SetBoneNode(AbstractPowerShipNode):
def execute(self, depsgraph: bpy.types.Depsgraph) -> None: def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
control_location = self._get_optional_input_value("Location", Vector) control_location = self._get_optional_input_value("Location", Vector)
control_rotation = self._get_optional_input_value("Rotation", Quaternion) control_rotation = self._get_optional_input_value(
"Rotation", Quaternion)
control_scale = self._get_optional_input_value("Scale", Vector) control_scale = self._get_optional_input_value("Scale", Vector)
if not (control_location or control_rotation or control_scale): if not (control_location or control_rotation or control_scale):
@ -651,7 +1107,8 @@ class SetBoneNode(AbstractPowerShipNode):
bone_rest_rot_scale = bone.bone.matrix_local.copy() bone_rest_rot_scale = bone.bone.matrix_local.copy()
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
@ -695,7 +1152,8 @@ class TwoBoneIKNode(AbstractPowerShipNode):
locator: Callable[[str, Vector], bpy.types.Object] locator: Callable[[str, Vector], bpy.types.Object]
if self._debug: if self._debug:
locator = functools.partial(self._debug_locator, scene=depsgraph.scene) locator = functools.partial(
self._debug_locator, scene=depsgraph.scene)
else: else:
locator = self._locator locator = self._locator
@ -703,9 +1161,11 @@ class TwoBoneIKNode(AbstractPowerShipNode):
target_loc = self._get_input_value("Target", Vector) target_loc = self._get_input_value("Target", Vector)
target_ob = locator("temp-ik-target", target_loc) target_ob = locator("temp-ik-target", target_loc)
pole_target_loc = self._get_optional_input_value("Pole Target", Vector) pole_target_loc = self._get_optional_input_value(
"Pole Target", Vector)
if pole_target_loc is not None: if pole_target_loc is not None:
pole_target_ob = locator("temp-ik-pole-target", pole_target_loc) pole_target_ob = locator(
"temp-ik-pole-target", pole_target_loc)
try: try:
# Reuse constraint from previous run. This can be useful for # Reuse constraint from previous run. This can be useful for
@ -1015,14 +1475,24 @@ node_categories = [
"MATH", "MATH",
"Math", "Math",
items=[ items=[
nodeitems_utils.NodeItem("AddNode"), nodeitems_utils.NodeItem("RotateTowards"),
nodeitems_utils.NodeItem("SubtractNode"), nodeitems_utils.NodeItem("OffsetRotation"),
nodeitems_utils.NodeItem("MultiplyNode"), nodeitems_utils.NodeItem("RotationFromAngle"),
nodeitems_utils.NodeItem("DivideNode"), nodeitems_utils.NodeItem("NormalFromPoints"),
nodeitems_utils.NodeItem("SplitVector"),
nodeitems_utils.NodeItem("ToVector"),
nodeitems_utils.NodeItem("Distance"),
nodeitems_utils.NodeItem("MapRange"),
nodeitems_utils.NodeItem("VectorMath"),
nodeitems_utils.NodeItem("Math"),
# nodeitems_utils.NodeItem("AddNode"),
# nodeitems_utils.NodeItem("SubtractNode"),
# nodeitems_utils.NodeItem("MultiplyNode"),
# nodeitems_utils.NodeItem("DivideNode"),
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",
@ -1040,6 +1510,10 @@ classes = (
# Socket types: # Socket types:
NodeSocketExecute, NodeSocketExecute,
NodeSocketQuaternion, NodeSocketQuaternion,
MathOperationSocket,
TrackAxisSocket,
UpAxisSocket,
AngleTypeSocket,
# Nodes: # Nodes:
ForwardSolveNode, ForwardSolveNode,
BackwardSolveNode, BackwardSolveNode,
@ -1050,13 +1524,24 @@ classes = (
TwoBoneIKNode, TwoBoneIKNode,
SetCursorNode, SetCursorNode,
SequenceNode, SequenceNode,
# Math Nodes
RotateTowards,
RotationFromAngle,
OffsetRotation,
NormalFromPoints,
ToVector,
SplitVector,
Distance,
MapRange,
VectorMath,
Math,
ToEulerNode, ToEulerNode,
FromEulerNode, FromEulerNode,
ClampNode, ClampNode,
AddNode, # AddNode,
SubtractNode, # SubtractNode,
MultiplyNode, # MultiplyNode,
DivideNode, # DivideNode,
# Operators: # Operators:
PowerShip_OT_rebuild_node, PowerShip_OT_rebuild_node,
) )
@ -1065,7 +1550,8 @@ _register, _unregister = bpy.utils.register_classes_factory(classes)
def register() -> None: def register() -> None:
_register() _register()
nodeitems_utils.register_node_categories("POWERSHIP_NODES", node_categories) nodeitems_utils.register_node_categories(
"POWERSHIP_NODES", node_categories)
def unregister() -> None: def unregister() -> None:

BIN
powership_transfer.blend Normal file

Binary file not shown.