Motion transfer setup #1
@ -146,6 +146,7 @@ def register() -> None:
|
|||||||
|
|
||||||
def unregister() -> None:
|
def unregister() -> None:
|
||||||
bpy.app.handlers.depsgraph_update_post.remove(_on_depsgraph_update_post)
|
bpy.app.handlers.depsgraph_update_post.remove(_on_depsgraph_update_post)
|
||||||
|
bpy.app.handlers.frame_change_post.remove(_on_frame_changed_post)
|
||||||
|
|
||||||
del bpy.types.Scene.powership_mode
|
del bpy.types.Scene.powership_mode
|
||||||
|
|
||||||
|
297
nodes.py
@ -67,10 +67,7 @@ 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:
|
||||||
try:
|
|
||||||
node.reset_run()
|
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"
|
||||||
@ -234,7 +231,6 @@ class AbstractPowerShipNode(bpy.types.Node):
|
|||||||
|
|
||||||
def run(self, depsgraph: bpy.types.Depsgraph) -> None:
|
def run(self, depsgraph: bpy.types.Depsgraph) -> None:
|
||||||
assert not self.has_run, "a node can only run once, reset it first"
|
assert not self.has_run, "a node can only run once, reset it first"
|
||||||
|
|
||||||
if self.mute:
|
if self.mute:
|
||||||
# Skip execution of this node, it's muted.
|
# Skip execution of this node, it's muted.
|
||||||
self._first_input_to_output()
|
self._first_input_to_output()
|
||||||
@ -278,7 +274,6 @@ class AbstractPowerShipNode(bpy.types.Node):
|
|||||||
|
|
||||||
cgtinker marked this conversation as resolved
Outdated
|
|||||||
def _first_input_to_output(self) -> None:
|
def _first_input_to_output(self) -> None:
|
||||||
"""Copy the first input's default value to the output, if the sockets are compatible."""
|
"""Copy the first input's default value to the output, if the sockets are compatible."""
|
||||||
|
|
||||||
if not self.inputs or not self.outputs:
|
if not self.inputs or not self.outputs:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -592,8 +587,6 @@ class ToVector(AbstractPowerShipNode):
|
|||||||
bl_icon = "EMPTY_ARROWS"
|
bl_icon = "EMPTY_ARROWS"
|
||||||
|
|
||||||
def init(self, context):
|
def init(self, context):
|
||||||
self.add_execution_sockets()
|
|
||||||
|
|
||||||
self.add_optional_input_socket("NodeSocketFloat", "X")
|
self.add_optional_input_socket("NodeSocketFloat", "X")
|
||||||
self.add_optional_input_socket("NodeSocketFloat", "Y")
|
self.add_optional_input_socket("NodeSocketFloat", "Y")
|
||||||
self.add_optional_input_socket("NodeSocketFloat", "Z")
|
self.add_optional_input_socket("NodeSocketFloat", "Z")
|
||||||
@ -621,8 +614,6 @@ class SplitVector(AbstractPowerShipNode):
|
|||||||
bl_icon = "EMPTY_ARROWS"
|
bl_icon = "EMPTY_ARROWS"
|
||||||
|
|
||||||
def init(self, context):
|
def init(self, context):
|
||||||
self.add_execution_sockets()
|
|
||||||
|
|
||||||
self.add_optional_input_socket("NodeSocketVector", "Vector")
|
self.add_optional_input_socket("NodeSocketVector", "Vector")
|
||||||
self.outputs.new("NodeSocketFloat", "X")
|
self.outputs.new("NodeSocketFloat", "X")
|
||||||
self.outputs.new("NodeSocketFloat", "Y")
|
self.outputs.new("NodeSocketFloat", "Y")
|
||||||
@ -630,10 +621,6 @@ class SplitVector(AbstractPowerShipNode):
|
|||||||
|
|
||||||
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._get_optional_input_value("Vector", Vector)
|
||||||
|
|
||||||
if not v:
|
|
||||||
return
|
|
||||||
|
|
||||||
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
|
||||||
@ -647,8 +634,6 @@ class Distance(AbstractPowerShipNode):
|
|||||||
bl_icon = "EMPTY_ARROWS"
|
bl_icon = "EMPTY_ARROWS"
|
||||||
|
|
||||||
def init(self, context):
|
def init(self, context):
|
||||||
self.add_execution_sockets()
|
|
||||||
|
|
||||||
self.add_optional_input_socket("NodeSocketVector", "U")
|
self.add_optional_input_socket("NodeSocketVector", "U")
|
||||||
self.add_optional_input_socket("NodeSocketVector", "V")
|
self.add_optional_input_socket("NodeSocketVector", "V")
|
||||||
self.outputs.new("NodeSocketFloat", "Float")
|
self.outputs.new("NodeSocketFloat", "Float")
|
||||||
@ -671,8 +656,6 @@ class NormalFromPoints(AbstractPowerShipNode):
|
|||||||
bl_icon = "EMPTY_ARROWS"
|
bl_icon = "EMPTY_ARROWS"
|
||||||
|
|
||||||
def init(self, context):
|
def init(self, context):
|
||||||
self.add_execution_sockets()
|
|
||||||
|
|
||||||
self.add_optional_input_socket("NodeSocketVector", "U")
|
self.add_optional_input_socket("NodeSocketVector", "U")
|
||||||
self.add_optional_input_socket("NodeSocketVector", "V")
|
self.add_optional_input_socket("NodeSocketVector", "V")
|
||||||
self.add_optional_input_socket("NodeSocketVector", "W")
|
self.add_optional_input_socket("NodeSocketVector", "W")
|
||||||
cgtinker marked this conversation as resolved
Outdated
Sybren A. Stüvel
commented
This only outputs a normal vector when This only outputs a normal vector when `a` and `b` are perpendicular. Better to use `a.cross(b).normalized()`
|
|||||||
@ -682,56 +665,19 @@ class NormalFromPoints(AbstractPowerShipNode):
|
|||||||
u = self._get_optional_input_value("U", Vector)
|
u = self._get_optional_input_value("U", Vector)
|
||||||
v = self._get_optional_input_value("V", Vector)
|
v = self._get_optional_input_value("V", Vector)
|
||||||
w = self._get_optional_input_value("W", Vector)
|
w = self._get_optional_input_value("W", Vector)
|
||||||
|
|
||||||
if not (u and v and w):
|
|
||||||
return
|
|
||||||
|
|
||||||
a = v - u
|
a = v - u
|
||||||
b = w - u
|
b = w - u
|
||||||
normal = a.cross(b)
|
normal = a.cross(b)
|
||||||
self.outputs["Result"].default_value = normal
|
self.outputs["Result"].default_value = normal
|
||||||
|
|
||||||
|
|
||||||
class UpAxisSocket(bpy.types.NodeSocket):
|
_enum_up_axis_items = (
|
||||||
'''Custom node socket type'''
|
|
||||||
bl_idname = 'UpAxisSocket'
|
|
||||||
bl_label = "Up Axis Socket"
|
|
||||||
link_limit = 0
|
|
||||||
|
|
||||||
# Enum items list
|
|
||||||
axis_items = (
|
|
||||||
('X', "X", ""),
|
('X', "X", ""),
|
||||||
('Y', "Y", ""),
|
('Y', "Y", ""),
|
||||||
('Z', "Z", ""),
|
('Z', "Z", ""),
|
||||||
)
|
)
|
||||||
# default_value = "UP"
|
|
||||||
|
|
||||||
default_value: bpy.props.EnumProperty(
|
_enum_track_axis_items = (
|
||||||
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", ""),
|
('X', "X", ""),
|
||||||
('Y', "Y", ""),
|
('Y', "Y", ""),
|
||||||
('Z', "Z", ""),
|
('Z', "Z", ""),
|
||||||
cgtinker marked this conversation as resolved
Outdated
Sybren A. Stüvel
commented
Follow PEP 257 (in this case, the initial line should be shorter, and the leading/trailing spaces should be removed). Same for other docstrings. Wellll.... try to follow PEP 257 but also keep in mind that Blender uses those docstrings for the tooltips, and thus they shouldn't end in a period. Stupid, I know... Follow PEP 257 (in this case, the initial line should be shorter, and the leading/trailing spaces should be removed). Same for other docstrings.
Wellll.... try to follow PEP 257 but also keep in mind that Blender uses those docstrings for the tooltips, and thus they shouldn't end in a period. Stupid, I know...
|
|||||||
@ -739,26 +685,6 @@ class TrackAxisSocket(bpy.types.NodeSocket):
|
|||||||
('-Y', "-Y", ""),
|
('-Y', "-Y", ""),
|
||||||
('-Z', "-Z", ""),
|
('-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):
|
class RotateTowards(AbstractPowerShipNode):
|
||||||
@ -768,12 +694,24 @@ class RotateTowards(AbstractPowerShipNode):
|
|||||||
bl_label = "Rotate Towards"
|
bl_label = "Rotate Towards"
|
||||||
bl_icon = "EMPTY_ARROWS"
|
bl_icon = "EMPTY_ARROWS"
|
||||||
|
|
||||||
|
track: bpy.props.EnumProperty( # type: ignore
|
||||||
|
name="Track",
|
||||||
Sybren A. Stüvel
commented
Why is Why is `Y` the default up-vector, and not `Z`?
Denys Hsu
commented
I thought it was like that in the python interface (and I was wrong). I thought it was like that in the python interface (and I was wrong).
Just checked in _mathutils_Vector.c_, there the up axis is **Y** and the track axis is **Z**. Should we follow that convention or go for **Z** as up axis and **X** as track axis?
|
|||||||
|
items=_enum_track_axis_items,
|
||||||
|
)
|
||||||
|
|
||||||
|
up: bpy.props.EnumProperty( # type: ignore
|
||||||
|
name="Up",
|
||||||
|
items=_enum_up_axis_items,
|
||||||
|
)
|
||||||
|
|
||||||
|
def draw_buttons(
|
||||||
|
self, context: bpy.types.Context, layout: bpy.types.UILayout
|
||||||
|
) -> None:
|
||||||
|
super().draw_buttons(context, layout)
|
||||||
cgtinker marked this conversation as resolved
Outdated
Sybren A. Stüvel
commented
I think these names could be improved for clarity. How about "Vector" and "Rotate To"? I think these names could be improved for clarity. How about "Vector" and "Rotate To"?
|
|||||||
|
layout.prop(self, "up")
|
||||||
|
layout.prop(self, "track")
|
||||||
|
|
||||||
def init(self, context):
|
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", "Origin")
|
||||||
self.add_optional_input_socket("NodeSocketVector", "Destination")
|
self.add_optional_input_socket("NodeSocketVector", "Destination")
|
||||||
self.outputs.new("NodeSocketQuaternion", "Rotation")
|
self.outputs.new("NodeSocketQuaternion", "Rotation")
|
||||||
@ -781,16 +719,14 @@ class RotateTowards(AbstractPowerShipNode):
|
|||||||
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._get_optional_input_value("Origin", Vector)
|
||||||
destination = self._get_optional_input_value("Destination", 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):
|
if not (origin and destination):
|
||||||
return
|
return Quaternion()
|
||||||
|
|
||||||
# Set the rotation of the control
|
# Set the rotation of the control
|
||||||
vec = Vector((destination - origin))
|
vec = Vector((destination - origin))
|
||||||
vec.normalize()
|
# vec.normalize()
|
||||||
cgtinker marked this conversation as resolved
Outdated
Sybren A. Stüvel
commented
`default_value` doesn't seem used.
|
|||||||
rot = vec.to_track_quat(track, up)
|
rot = vec.to_track_quat(self.track, self.up)
|
||||||
|
rot.normalize()
|
||||||
self.outputs["Rotation"].default_value = rot
|
self.outputs["Rotation"].default_value = rot
|
||||||
|
|
||||||
|
|
||||||
@ -800,9 +736,9 @@ 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_execution_sockets()
|
|
||||||
self.add_optional_input_socket("NodeSocketQuaternion", "Base")
|
self.add_optional_input_socket("NodeSocketQuaternion", "Base")
|
||||||
self.add_optional_input_socket("NodeSocketQuaternion", "Offset")
|
self.add_optional_input_socket("NodeSocketQuaternion", "Offset")
|
||||||
self.outputs.new("NodeSocketQuaternion", "Rotation")
|
self.outputs.new("NodeSocketQuaternion", "Rotation")
|
||||||
@ -810,10 +746,6 @@ class OffsetRotation(AbstractPowerShipNode):
|
|||||||
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._get_optional_input_value("Base", Quaternion)
|
||||||
offset = self._get_optional_input_value("Offset", Quaternion)
|
offset = self._get_optional_input_value("Offset", Quaternion)
|
||||||
|
|
||||||
if not (base and offset):
|
|
||||||
return
|
|
||||||
|
|
||||||
self.outputs["Rotation"].default_value = base @ offset
|
self.outputs["Rotation"].default_value = base @ offset
|
||||||
|
|
||||||
|
|
||||||
@ -825,8 +757,6 @@ class MapRange(AbstractPowerShipNode):
|
|||||||
bl_icon = "EMPTY_ARROWS"
|
bl_icon = "EMPTY_ARROWS"
|
||||||
|
|
||||||
def init(self, context):
|
def init(self, context):
|
||||||
self.add_execution_sockets()
|
|
||||||
|
|
||||||
self.add_optional_input_socket("NodeSocketFloat", "Value")
|
self.add_optional_input_socket("NodeSocketFloat", "Value")
|
||||||
self.add_optional_input_socket("NodeSocketFloat", "From Min")
|
self.add_optional_input_socket("NodeSocketFloat", "From Min")
|
||||||
self.add_optional_input_socket("NodeSocketFloat", "From Max")
|
self.add_optional_input_socket("NodeSocketFloat", "From Max")
|
||||||
@ -841,55 +771,37 @@ class MapRange(AbstractPowerShipNode):
|
|||||||
tmin = self._get_optional_input_value("Value", float)
|
tmin = self._get_optional_input_value("Value", float)
|
||||||
tmax = 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)
|
slope = (tmax - tmin) / (fmax - fmin)
|
||||||
offset = tmin - slope * fmin
|
offset = tmin - slope * fmin
|
||||||
Sybren A. Stüvel
commented
Why name it Why name it `DEFAULT` instead of `UNSIGNED`? And why is `UNSIGNED` the default value? Not saying that it should be the other one, just wondering about your thought process.
Denys Hsu
commented
Usually an angle is not signed, you got to define something to sign it. That's why I thought unsigned angles should be the default. I rechecked and to be honest, I neither knew that the "signed" vector method is only for 2D vectors nor how it actually signed an angle. So I guess I removed it and kept only the "unsigned" angle for now - my bad sorry. Kinda liked the idea of an easy to use signed angle. So far, when I needed a signed angle, I ended up using a plane. Basically I've used the signed distance (based on the normal) from the plane to the destination of the vector and used the sign for the angle. This is possible with the current system without changes so I guess it's fine. Usually an angle is not signed, you got to define something to sign it. That's why I thought unsigned angles should be the default.
I rechecked and to be honest, I neither knew that the "signed" vector method is only for 2D vectors nor how it actually signed an angle. So I guess I removed it and kept only the "unsigned" angle for now - my bad sorry. Kinda liked the idea of an easy to use signed angle.
So far, when I needed a signed angle, I ended up using a plane. Basically I've used the signed distance (based on the normal) from the plane to the destination of the vector and used the sign for the angle. This is possible with the current system without changes so I guess it's fine.
|
|||||||
self.outputs["Result"].default_value = slope * val + offset
|
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):
|
class RotationFromAngle(AbstractPowerShipNode):
|
||||||
bl_idname = "RotationFromAngle"
|
bl_idname = "RotationFromAngle"
|
||||||
bl_label = "Rotation From Vector Angle"
|
bl_label = "Rotation From Vector Angle"
|
||||||
bl_icon = "EMPTY_ARROWS"
|
bl_icon = "EMPTY_ARROWS"
|
||||||
|
|
||||||
|
axis: bpy.props.EnumProperty( # type: ignore
|
||||||
|
name="Axis",
|
||||||
|
items=_enum_up_axis_items,
|
||||||
|
)
|
||||||
|
|
||||||
|
angle_type: bpy.props.EnumProperty( # type: ignore
|
||||||
|
name="Type",
|
||||||
|
items=[
|
||||||
|
("DEFAULT", "Default", ""),
|
||||||
|
("SIGNED", "Signed", ""),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def draw_buttons(
|
||||||
cgtinker marked this conversation as resolved
Outdated
Sybren A. Stüvel
commented
Don't compute Don't compute `length` when you can use `length_squared` as well.
|
|||||||
|
self, context: bpy.types.Context, layout: bpy.types.UILayout
|
||||||
|
) -> None:
|
||||||
|
super().draw_buttons(context, layout)
|
||||||
|
layout.prop(self, "axis")
|
||||||
|
layout.prop(self, "angle_type")
|
||||||
|
|
||||||
def init(self, context):
|
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", "U")
|
||||||
self.add_optional_input_socket("NodeSocketVector", "V")
|
self.add_optional_input_socket("NodeSocketVector", "V")
|
||||||
self.outputs.new("NodeSocketQuaternion", "Rotation")
|
self.outputs.new("NodeSocketQuaternion", "Rotation")
|
||||||
@ -897,60 +809,28 @@ class RotationFromAngle(AbstractPowerShipNode):
|
|||||||
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._get_optional_input_value("U", Vector)
|
||||||
v = self._get_optional_input_value("V", Vector)
|
v = self._get_optional_input_value("V", Vector)
|
||||||
axis = self._get_input_value("Axis", str)
|
signed = self.angle_type == 'SIGNED'
|
||||||
angle_type = self._get_input_value("Type", str)
|
|
||||||
signed = angle_type == 'SIGNED'
|
|
||||||
|
|
||||||
if not (u and v):
|
|
||||||
return
|
|
||||||
|
|
||||||
angle = 0
|
angle = 0
|
||||||
|
|
||||||
if not (u.length == 0 or v.length == 0):
|
if not (u.length == 0 or v.length == 0):
|
||||||
if signed:
|
if signed:
|
||||||
angle = u.angle_signed(v)
|
angle = u.angle_signed(v)
|
||||||
else:
|
else:
|
||||||
angle = u.angle(v)
|
angle = u.angle(v)
|
||||||
|
|
||||||
m = Matrix.Rotation(angle, 3, axis)
|
m = Matrix.Rotation(angle, 3, self.axis)
|
||||||
res = m.to_quaternion()
|
res = m.to_quaternion()
|
||||||
|
|
||||||
self.outputs["Rotation"].default_value = res
|
self.outputs["Rotation"].default_value = res
|
||||||
|
|
||||||
|
|
||||||
class MathOperationSocket(bpy.types.NodeSocket):
|
_enum_vector_math_operations = [
|
||||||
bl_idname = 'MathOperationSocket'
|
|
||||||
bl_label = "Operations"
|
|
||||||
link_limit = 0
|
|
||||||
|
|
||||||
# Enum items list
|
|
||||||
ops_items = (
|
|
||||||
('ADD', "Add", ""),
|
('ADD', "Add", ""),
|
||||||
('SUBSTRACT', "Substract", ""),
|
('SUBSTRACT', "Substract", ""),
|
||||||
('MULTIPLY', "Mutliply", ""),
|
('MULTIPLY', "Mutliply", ""),
|
||||||
('DIVIDE', "Divide", ""),
|
('DIVIDE', "Divide", ""),
|
||||||
('CROSS', "Cross", ""),
|
('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):
|
class VectorMath(AbstractPowerShipNode):
|
||||||
@ -960,9 +840,18 @@ class VectorMath(AbstractPowerShipNode):
|
|||||||
bl_label = "Vector Math"
|
bl_label = "Vector Math"
|
||||||
bl_icon = "EMPTY_ARROWS"
|
bl_icon = "EMPTY_ARROWS"
|
||||||
|
|
||||||
|
operation: bpy.props.EnumProperty( # type: ignore
|
||||||
|
name="Operation",
|
||||||
|
items=_enum_vector_math_operations,
|
||||||
|
)
|
||||||
|
|
||||||
|
def draw_buttons(
|
||||||
cgtinker marked this conversation as resolved
Outdated
Sybren A. Stüvel
commented
This could be an interesting design discussion. I actually had a little design discussion with @nathanvegdahl about this, and until we have a 'debug mode' that can visualise where NaNs are produced, it's better to stick to regular floats. So in this case And another minor thing: the list comprehension can be replaced by a generator expression for a little bit of added performance and readability:
This could be an interesting design discussion. ~~Not something to do here -- this code is fine.~~ But in general, we have to decide how Powership is going to handle erroneous values. For example,. the `Vector.normalized()` function I suggested above will simply return `(0, 0, 0)` for zero vectors. I feel that maybe here it could also make sense to define `x / 0 → 0` and return as many usable values as possible.
I actually had a little design discussion with @nathanvegdahl about this, and until we have a 'debug mode' that can visualise where NaNs are produced, it's better to stick to regular floats. So in this case `x/y if y != 0.0 else 0.0`
And another minor thing: the list comprehension can be replaced by a generator expression for a little bit of added performance and readability:
```py
res = Vector(x/y if y != 0.0 else 0.0 for x, y in zip(u, v))
```
|
|||||||
|
self, context: bpy.types.Context, layout: bpy.types.UILayout
|
||||||
|
) -> None:
|
||||||
|
super().draw_buttons(context, layout)
|
||||||
|
layout.prop(self, "operation")
|
||||||
|
|
||||||
cgtinker marked this conversation as resolved
Outdated
Sybren A. Stüvel
commented
Either log/print, or raise an exception. Don't do both, as one issue will be reported twice. Either log/print, or raise an exception. Don't do both, as one issue will be reported twice.
|
|||||||
def init(self, context):
|
def init(self, context):
|
||||||
cgtinker marked this conversation as resolved
Outdated
Sybren A. Stüvel
commented
Don't use a class as exception, always instantiate it.
Don't use a class as exception, always instantiate it.
```py
raise ValueError(f"Vector math operation not found: {self.operation!r}")
```
|
|||||||
self.add_execution_sockets()
|
|
||||||
self.add_optional_input_socket("MathOperationSocket", "Operation")
|
|
||||||
self.add_optional_input_socket("NodeSocketVector", "U")
|
self.add_optional_input_socket("NodeSocketVector", "U")
|
||||||
self.add_optional_input_socket("NodeSocketVector", "V")
|
self.add_optional_input_socket("NodeSocketVector", "V")
|
||||||
self.outputs.new("NodeSocketVector", "Result")
|
self.outputs.new("NodeSocketVector", "Result")
|
||||||
@ -970,12 +859,8 @@ class VectorMath(AbstractPowerShipNode):
|
|||||||
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._get_optional_input_value("U", Vector)
|
||||||
v = self._get_optional_input_value("V", Vector)
|
v = self._get_optional_input_value("V", Vector)
|
||||||
mode = self._get_input_value("Operation", str)
|
|
||||||
|
|
||||||
if not (u and v):
|
match self.operation:
|
||||||
return
|
|
||||||
|
|
||||||
match mode:
|
|
||||||
case 'ADD':
|
case 'ADD':
|
||||||
res = u+v
|
res = u+v
|
||||||
case 'MULTIPLY':
|
case 'MULTIPLY':
|
||||||
@ -983,15 +868,24 @@ class VectorMath(AbstractPowerShipNode):
|
|||||||
case 'SUBSTRACT':
|
case 'SUBSTRACT':
|
||||||
res = u-v
|
res = u-v
|
||||||
case 'DIVIDE':
|
case 'DIVIDE':
|
||||||
res = Vector([x/y for x, y in zip(u, v)])
|
res = Vector([x/y if y != 0.0 else 0.0 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
|
raise ValueError
|
||||||
|
|
||||||
self.outputs["Result"].default_value = res
|
self.outputs["Result"].default_value = res
|
||||||
|
|
||||||
|
|
||||||
|
_enum_math_operations = [
|
||||||
|
('ADD', "Add", ""),
|
||||||
|
('SUBSTRACT', "Substract", ""),
|
||||||
|
('MULTIPLY', "Mutliply", ""),
|
||||||
|
('DIVIDE', "Divide", ""),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class Math(AbstractPowerShipNode):
|
class Math(AbstractPowerShipNode):
|
||||||
"""Sets the location and/or rotation of the 3D cursor"""
|
"""Sets the location and/or rotation of the 3D cursor"""
|
||||||
|
|
||||||
@ -999,9 +893,18 @@ class Math(AbstractPowerShipNode):
|
|||||||
bl_label = "Math"
|
bl_label = "Math"
|
||||||
bl_icon = "EMPTY_ARROWS"
|
bl_icon = "EMPTY_ARROWS"
|
||||||
|
|
||||||
|
operation: bpy.props.EnumProperty( # type: ignore
|
||||||
|
name="Operation",
|
||||||
|
items=_enum_math_operations,
|
||||||
|
)
|
||||||
|
|
||||||
cgtinker marked this conversation as resolved
Outdated
Sybren A. Stüvel
commented
nan → 0.0 nan → 0.0
|
|||||||
|
def draw_buttons(
|
||||||
|
self, context: bpy.types.Context, layout: bpy.types.UILayout
|
||||||
|
) -> None:
|
||||||
cgtinker marked this conversation as resolved
Outdated
Sybren A. Stüvel
commented
Same as above. Same as above.
|
|||||||
|
super().draw_buttons(context, layout)
|
||||||
|
layout.prop(self, "operation")
|
||||||
|
|
||||||
def init(self, context):
|
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", "U")
|
||||||
self.add_optional_input_socket("NodeSocketFloat", "V")
|
self.add_optional_input_socket("NodeSocketFloat", "V")
|
||||||
self.outputs.new("NodeSocketFloat", "Result")
|
self.outputs.new("NodeSocketFloat", "Result")
|
||||||
@ -1009,12 +912,8 @@ class Math(AbstractPowerShipNode):
|
|||||||
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._get_optional_input_value("U", float)
|
||||||
v = self._get_optional_input_value("V", float)
|
v = self._get_optional_input_value("V", float)
|
||||||
mode = self._get_input_value("Operation", str)
|
|
||||||
|
|
||||||
if not (u and v):
|
match self.operation:
|
||||||
return
|
|
||||||
|
|
||||||
match mode:
|
|
||||||
case 'ADD':
|
case 'ADD':
|
||||||
res = u+v
|
res = u+v
|
||||||
case 'MULTIPLY':
|
case 'MULTIPLY':
|
||||||
@ -1022,9 +921,9 @@ class Math(AbstractPowerShipNode):
|
|||||||
case 'SUBSTRACT':
|
case 'SUBSTRACT':
|
||||||
res = u-v
|
res = u-v
|
||||||
case 'DIVIDE':
|
case 'DIVIDE':
|
||||||
res = u/v
|
res = u/v if v != 0 else 0
|
||||||
case _:
|
case _:
|
||||||
print("MODE NOT FOUND:", mode)
|
print("Math operation not found:", self.operation)
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
self.outputs["Result"].default_value = res
|
self.outputs["Result"].default_value = res
|
||||||
@ -1062,6 +961,17 @@ class SetBoneNode(AbstractPowerShipNode):
|
|||||||
bl_label = "Set Bone"
|
bl_label = "Set Bone"
|
||||||
bl_icon = "BONE_DATA"
|
bl_icon = "BONE_DATA"
|
||||||
|
|
||||||
|
space: bpy.props.EnumProperty( # type: ignore
|
||||||
|
name="Space",
|
||||||
|
items=_enum_control_space,
|
||||||
|
)
|
||||||
|
|
||||||
|
def draw_buttons(
|
||||||
|
self, context: bpy.types.Context, layout: bpy.types.UILayout
|
||||||
|
) -> None:
|
||||||
|
super().draw_buttons(context, layout)
|
||||||
|
layout.prop(self, "space")
|
||||||
|
|
||||||
def init(self, context):
|
def init(self, context):
|
||||||
self.add_execution_sockets()
|
self.add_execution_sockets()
|
||||||
|
|
||||||
@ -1103,13 +1013,20 @@ class SetBoneNode(AbstractPowerShipNode):
|
|||||||
if control_scale is not None:
|
if control_scale is not None:
|
||||||
scale = control_scale
|
scale = control_scale
|
||||||
|
|
||||||
|
match self.space:
|
||||||
|
case "WORLD":
|
||||||
|
bone_mat_world = Matrix.LocRotScale(loc, rot, scale)
|
||||||
|
case "CHANNELS":
|
||||||
|
# Not sure what causes the scale to flip, however...
|
||||||
|
# The y/z-scale flips on update if multiple rotations are chained,
|
||||||
|
# this generates jitter when running const updates - flipping seems to temp fix it.
|
||||||
|
scale = [scale[i] for i in [0, 2, 1]]
|
||||||
v_nil = Vector((0, 0, 0))
|
v_nil = Vector((0, 0, 0))
|
||||||
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
|
||||||
|
bone_rest_rot = bone_rest_rot_scale.to_quaternion()
|
||||||
mat_rot_scale = Matrix.LocRotScale(
|
mat_rot_scale = Matrix.LocRotScale(
|
||||||
v_nil, rot, scale) @ bone_rest_rot_scale
|
v_nil, rot, scale) @ bone_rest_rot.to_matrix().to_4x4()
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@ -1510,10 +1427,6 @@ classes = (
|
|||||||
# Socket types:
|
# Socket types:
|
||||||
NodeSocketExecute,
|
NodeSocketExecute,
|
||||||
NodeSocketQuaternion,
|
NodeSocketQuaternion,
|
||||||
MathOperationSocket,
|
|
||||||
TrackAxisSocket,
|
|
||||||
UpAxisSocket,
|
|
||||||
AngleTypeSocket,
|
|
||||||
# Nodes:
|
# Nodes:
|
||||||
ForwardSolveNode,
|
ForwardSolveNode,
|
||||||
BackwardSolveNode,
|
BackwardSolveNode,
|
||||||
|
Keep formatting changes out of the patch. You can use
git gui
or some other tool to cherry-pick which lines you do (not) want to include in a commit. That way you can exclude such changes, commit the rest, then revert the unwanted formatting changes to get rid of them.