Motion transfer setup #1
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,6 @@
|
|||||||
|
/.venv
|
||||||
|
/.vscode
|
||||||
__pycache__
|
__pycache__
|
||||||
|
*.DS_Store
|
||||||
*.pyc
|
*.pyc
|
||||||
*.blend1
|
*.blend1
|
||||||
|
36
execute.py
36
execute.py
@ -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
|
|||||||
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
538
nodes.py
@ -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
Sybren A. Stüvel
commented
Does this calculate a normal vector? Or its length? Does this calculate a normal vector? Or its length?
Denys Hsu
commented
The length. 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
BIN
powership_transfer.blend
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user
Since
_skip_next_autorun
is a boolean, theif
condition can be removed.