2025-07-01
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_BooleanMathNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_BooleanMathNode"
|
||||
bl_label = "Boolean Math"
|
||||
node_color = "BOOLEAN"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_boolean_input("Boolean")
|
||||
self.add_boolean_input("Boolean")
|
||||
self.add_dynamic_boolean_input("Boolean")
|
||||
self.add_boolean_output("Boolean")
|
||||
|
||||
|
||||
def update_operation(self, context):
|
||||
for inp in self.inputs[1:]:
|
||||
inp.enabled = self.operation != "NOT"
|
||||
self._evaluate(context)
|
||||
|
||||
operation: bpy.props.EnumProperty(items=[("AND", "And", "Returns True if both inputs are True"),
|
||||
("OR", "Or", "Returns True if one or both inputs are True"),
|
||||
("NOT", "Not", "Returns True if the inputs is False and False if the input is True")],
|
||||
name="Operation",
|
||||
description="Operation to perform on the input booleans",
|
||||
update=update_operation)
|
||||
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "operation", text='')
|
||||
|
||||
def evaluate(self, context):
|
||||
# not operation
|
||||
if self.operation == "NOT":
|
||||
self.outputs["Boolean"].python_value = f" not {self.inputs[0].python_value}"
|
||||
# and or or operation
|
||||
else:
|
||||
# get all input values
|
||||
values = []
|
||||
for inp in self.inputs[:-1]:
|
||||
if inp.enabled:
|
||||
values.append(inp.python_value)
|
||||
# join input values on operation name
|
||||
join_op = f" {'and' if self.operation == 'AND' else 'or'} ".join(values)
|
||||
self.outputs["Boolean"].python_value = f"({join_op})"
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_InvertBooleanNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_InvertBooleanNode"
|
||||
bl_label = "Invert Boolean"
|
||||
node_color = "BOOLEAN"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_boolean_input("Boolean")
|
||||
self.add_boolean_output("Boolean")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs[0].python_value = f"(not {self.inputs[0].python_value})"
|
||||
@@ -0,0 +1,33 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_CompareNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_CompareNode"
|
||||
bl_label = "Compare"
|
||||
node_color = "BOOLEAN"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_data_input("Data")
|
||||
self.add_data_input("Data")
|
||||
self.add_boolean_output("Boolean")
|
||||
|
||||
|
||||
operation: bpy.props.EnumProperty(items=[("==", "=", "Equal"),
|
||||
("!=", "≠", "Not equal"),
|
||||
("<", "<", "Smaller than"),
|
||||
(">", ">", "Bigger than"),
|
||||
("<=", "≤", "Smaller or equal to"),
|
||||
(">=", "≥", "Bigger or equal to")],
|
||||
name="Operation",
|
||||
description="Operation to perform on the input data",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "operation", text='')
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs["Boolean"].python_value = f"({self.inputs[0].python_value} {self.operation} {self.inputs[1].python_value})"
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_3DLocationTo2DNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_3DLocationTo2DNode"
|
||||
bl_label = "3D View Coordinates To 2D"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_property_input("Area")
|
||||
self.add_float_vector_input("Coordinates").size = 3
|
||||
self.add_float_vector_output("2D Coordinates")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_import = "from bpy_extras.view3d_utils import location_3d_to_region_2d"
|
||||
self.outputs[0].python_value = f"location_3d_to_region_2d({self.inputs[0].python_value}.regions[5], {self.inputs[0].python_value}.spaces[0].region_3d, {self.inputs[1].python_value})"
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
import bpy
|
||||
import string
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_CombineVectorNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_CombineVectorNode"
|
||||
bl_label = "Combine Vector"
|
||||
node_color = "VECTOR"
|
||||
|
||||
def update_vector_type(self,context):
|
||||
self.convert_socket(self.outputs[0], self.socket_names[self.vector_type])
|
||||
for input in self.inputs:
|
||||
self.convert_socket(input, self.socket_names[self.vector_type[:-7]])
|
||||
self._evaluate(context)
|
||||
|
||||
def update_size(self,context):
|
||||
self.outputs[0].size = self.size
|
||||
if len(self.inputs) > self.size:
|
||||
for i in range(len(self.inputs)-self.size):
|
||||
self.inputs.remove(self.inputs[-1])
|
||||
elif self.size > len(self.inputs):
|
||||
for i in range(self.size-len(self.inputs)):
|
||||
alphabet = list(string.ascii_lowercase) + list(string.ascii_uppercase)
|
||||
if self.vector_type == "Float Vector":
|
||||
self.add_float_input(alphabet[len(self.inputs)])
|
||||
elif self.vector_type == "Integer Vector":
|
||||
self.add_integer_input(alphabet[len(self.inputs)])
|
||||
elif self.vector_type == "Boolean Vector":
|
||||
self.add_boolean_input(alphabet[len(self.inputs)])
|
||||
self._evaluate(context)
|
||||
|
||||
vector_type: bpy.props.EnumProperty(name="Type",
|
||||
description="The type of vector that should be used",
|
||||
items=[("Float Vector","Float","Float Vector"),
|
||||
("Integer Vector","Integer","Integer Vector"),
|
||||
("Boolean Vector","Boolean","Boolean Vector")],
|
||||
update=update_vector_type)
|
||||
|
||||
size: bpy.props.IntProperty(default=3, min=2, max=32,
|
||||
name="Size",
|
||||
description="Size of this the vector",
|
||||
update=update_size)
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_float_vector_output("Vector")
|
||||
self.add_float_input("a")
|
||||
self.add_float_input("b")
|
||||
self.add_float_input("c")
|
||||
|
||||
|
||||
def evaluate(self, context):
|
||||
values = [inp.python_value for inp in self.inputs]
|
||||
self.outputs[0].python_value = f"({', '.join(values)})"
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "vector_type")
|
||||
layout.prop(self, "size")
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_DataToIconNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_DataToIconNode"
|
||||
bl_label = "Data To Icon"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_string_input("Name")
|
||||
self.add_icon_output("Icon")
|
||||
|
||||
data_type: bpy.props.EnumProperty(name="Type",
|
||||
description="Type of data to preview",
|
||||
items=[("bpy.data.materials", "Material", "Material"),
|
||||
("bpy.data.objects", "Object", "Object"),
|
||||
("bpy.data.images", "Image", "Image"),
|
||||
("bpy.data.textures", "Texture", "Texture")],
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_imperative = f"""
|
||||
def get_id_preview_id(data):
|
||||
if hasattr(data, "preview"):
|
||||
if not data.preview:
|
||||
data.preview_ensure()
|
||||
if hasattr(data.preview, "icon_id"):
|
||||
return data.preview.icon_id
|
||||
return 0
|
||||
"""
|
||||
self.outputs["Icon"].python_value = f"get_id_preview_id({self.data_type}[{self.inputs['Name'].python_value}])"
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.label(text="Use this node with care! This might slow down your UI", icon="INFO")
|
||||
layout.prop(self, "data_type")
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
class SN_DefineDataType(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_DefineDataType"
|
||||
bl_label = "Define Data Type"
|
||||
node_color = "DEFAULT"
|
||||
|
||||
def get_data_items(self,context):
|
||||
items = []
|
||||
for label in list(self.socket_names.keys())[3:]:
|
||||
items.append((self.socket_names[label], label, self.socket_names[label]))
|
||||
return items
|
||||
|
||||
def update_conversion(self, context):
|
||||
self.convert_socket(self.outputs[0], self.convert_to)
|
||||
|
||||
convert_to: bpy.props.EnumProperty(items=get_data_items,
|
||||
update=update_conversion,
|
||||
name="Data",
|
||||
description="The type of data that you want to set the data input to",)
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "convert_to", text="")
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_data_input("Data")
|
||||
self.add_string_output("Data")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs[0].python_value = self.inputs[0].python_value
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
class SN_EnumSetToListNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_EnumSetToListNode"
|
||||
bl_label = "Enum Set To List"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_property_input("Enum Set Property")
|
||||
self.add_list_output("List")
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.inputs[0].is_linked:
|
||||
self.outputs[0].python_value = f"list({self.inputs[0].python_value})"
|
||||
else:
|
||||
self.outputs[0].reset_value()
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_RadiansNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_RadiansNode"
|
||||
bl_label = "Convert Radians/Degrees"
|
||||
node_color = "FLOAT"
|
||||
|
||||
def update_operation(self, context):
|
||||
if self.operation == "degrees":
|
||||
self.inputs[0].name = "Radians"
|
||||
self.outputs[0].name = "Degrees"
|
||||
else:
|
||||
self.inputs[0].name = "Degrees"
|
||||
self.outputs[0].name = "Radians"
|
||||
self._evaluate(context)
|
||||
|
||||
operation: bpy.props.EnumProperty(items=[("degrees", "Radians to Degrees", "Convert Radians to Degrees"), ("radians", "Degrees to Radians", "Convert Degrees to radians")],name="Operation", update=update_operation)
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_float_input("Radians")
|
||||
self.add_float_output("Degrees")
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "operation", text="")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_import = "import math"
|
||||
self.outputs[0].python_value = f"math.{self.operation}({self.inputs[0].python_value})"
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_RegionToViewNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_RegionToViewNode"
|
||||
bl_label = "Region To View"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_property_input("Area")
|
||||
self.add_float_vector_input("Coordinates").size = 2
|
||||
self.add_float_vector_output("View Coordinates")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_imperative = f"""
|
||||
def coords_region_to_view(area, coords):
|
||||
for region in area.regions:
|
||||
if region.type == "WINDOW":
|
||||
ui_scale = bpy.context.preferences.system.ui_scale
|
||||
x, y = region.view2d.region_to_view(coords[0], coords[1])
|
||||
return (x/ui_scale, y/ui_scale)
|
||||
return coords
|
||||
"""
|
||||
self.outputs[0].python_value = f"coords_region_to_view({self.inputs[0].python_value}, tuple({self.inputs[1].python_value}))"
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
import bpy
|
||||
import string
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_SplitVectorNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_SplitVectorNode"
|
||||
bl_label = "Split Vector"
|
||||
node_color = "VECTOR"
|
||||
|
||||
def update_vector_type(self,context):
|
||||
self.convert_socket(self.inputs[0], self.socket_names[self.vector_type])
|
||||
for output in self.outputs:
|
||||
self.convert_socket(output, self.socket_names[self.vector_type[:-7]])
|
||||
self._evaluate(context)
|
||||
|
||||
def update_size(self,context):
|
||||
self.inputs[0].size = self.size
|
||||
if len(self.outputs) > self.size:
|
||||
for i in range(len(self.outputs)-self.size):
|
||||
self.outputs.remove(self.outputs[-1])
|
||||
elif self.size > len(self.outputs):
|
||||
for i in range(self.size-len(self.outputs)):
|
||||
alphabet = list(string.ascii_lowercase) + list(string.ascii_uppercase)
|
||||
if self.vector_type == "Float Vector":
|
||||
self.add_float_output(alphabet[len(self.outputs)])
|
||||
elif self.vector_type == "Integer Vector":
|
||||
self.add_integer_output(alphabet[len(self.outputs)])
|
||||
elif self.vector_type == "Boolean Vector":
|
||||
self.add_boolean_output(alphabet[len(self.outputs)])
|
||||
self._evaluate(context)
|
||||
|
||||
vector_type: bpy.props.EnumProperty(name="Type",
|
||||
description="The type of vector that should be used",
|
||||
items=[("Float Vector","Float","Float Vector"),
|
||||
("Integer Vector","Integer","Integer Vector"),
|
||||
("Boolean Vector","Boolean","Boolean Vector")],
|
||||
update=update_vector_type)
|
||||
|
||||
size: bpy.props.IntProperty(default=3, min=2, max=32,
|
||||
name="Size",
|
||||
description="Size of this the vector",
|
||||
update=update_size)
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_float_vector_input("Vector")
|
||||
self.add_float_output("a")
|
||||
self.add_float_output("b")
|
||||
self.add_float_output("c")
|
||||
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.vector_type == "Integer Vector":
|
||||
for i in range(len(self.outputs)):
|
||||
self.outputs[i].python_value = f"int({self.inputs[0].python_value}[{i}])"
|
||||
else:
|
||||
for i in range(len(self.outputs)):
|
||||
self.outputs[i].python_value = f"{self.inputs[0].python_value}[{i}]"
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "vector_type")
|
||||
layout.prop(self, "size")
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_ViewToRegionNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_ViewToRegionNode"
|
||||
bl_label = "View To Region"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_property_input("Area")
|
||||
self.add_float_vector_input("Coordinates").size = 2
|
||||
self.add_float_vector_output("Region Coordinates")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_imperative = f"""
|
||||
def coords_view_to_region(area, coords):
|
||||
for region in area.regions:
|
||||
if region.type == "WINDOW":
|
||||
ui_scale = bpy.context.preferences.system.ui_scale
|
||||
return region.view2d.view_to_region(coords[0]*ui_scale, coords[1]*ui_scale, clip=False)
|
||||
return coords
|
||||
"""
|
||||
self.outputs[0].python_value = f"coords_view_to_region({self.inputs[0].python_value}, tuple({self.inputs[1].python_value}))"
|
||||
@@ -0,0 +1,20 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_JoinPathNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_JoinPathNode"
|
||||
bl_label = "Join Path"
|
||||
node_color = "STRING"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_string_input("Basepath").subtype = "DIR_PATH"
|
||||
self.add_dynamic_string_input("Path Part")
|
||||
self.add_string_output("Path").subtype = "FILE_PATH"
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_import = "import os"
|
||||
self.outputs[0].python_value = f"os.path.join({self.inputs[0].python_value},{','.join([inp.python_value for inp in self.inputs[1:-1]])})"
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
class SN_ListBlendContentNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
bl_idname = "SN_ListBlendContentNode"
|
||||
bl_label = "List Blend File Content"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_string_input("Path").subtype = "FILE_PATH"
|
||||
self.add_list_output("Names")
|
||||
|
||||
def get_append_types(self, context):
|
||||
items = [
|
||||
"actions",
|
||||
"armatures",
|
||||
"brushes",
|
||||
"cache_files",
|
||||
"cameras",
|
||||
"collections",
|
||||
"curves",
|
||||
"images",
|
||||
"fonts",
|
||||
"grease_pencils",
|
||||
"lattices",
|
||||
"libraries",
|
||||
"lightprobes",
|
||||
"lights",
|
||||
"linestyles",
|
||||
"masks",
|
||||
"materials",
|
||||
"meshes",
|
||||
"metaballs",
|
||||
"movieclips",
|
||||
"node_groups",
|
||||
"objects",
|
||||
"paint_curves",
|
||||
"palettes",
|
||||
"particles",
|
||||
"pointclouds",
|
||||
"scenes",
|
||||
"screens",
|
||||
"shape_keys",
|
||||
"sounds",
|
||||
"speakers",
|
||||
"texts",
|
||||
"textures",
|
||||
"volumes",
|
||||
"workspaces",
|
||||
"worlds",
|
||||
]
|
||||
|
||||
tuple_items = []
|
||||
for item in items:
|
||||
tuple_items.append((item, item.replace("_", " ").title(), item))
|
||||
return tuple_items
|
||||
|
||||
append_type: bpy.props.EnumProperty(
|
||||
items=get_append_types,
|
||||
name="Type",
|
||||
description="Type of the Append object",
|
||||
update=SN_ScriptingBaseNode._evaluate,
|
||||
)
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "append_type")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_imperative = """
|
||||
def get_blend_contents(path, data_type):
|
||||
if os.path.exists(path):
|
||||
with bpy.data.libraries.load(path) as (data_from, data_to):
|
||||
return getattr(data_from, data_type)
|
||||
return []
|
||||
"""
|
||||
self.code_import = "import os"
|
||||
self.outputs[
|
||||
0
|
||||
].python_value = (
|
||||
f"get_blend_contents({self.inputs[0].python_value}, '{self.append_type}')"
|
||||
)
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_ListDirectoryFilesNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_ListDirectoryFilesNode"
|
||||
bl_label = "List Directory Files"
|
||||
node_color = "STRING"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_string_input("Path").subtype = "FILE_PATH"
|
||||
self.add_list_output("Files + Directories")
|
||||
self.add_list_output("Files")
|
||||
self.add_list_output("Directories")
|
||||
|
||||
with_root: bpy.props.BoolProperty(name="With Path",
|
||||
description="Returns a list of full paths instead of just the file and directory names",
|
||||
default=True, update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "with_root")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_import = "import os"
|
||||
|
||||
if not self.with_root:
|
||||
self.outputs[0].python_value = f"os.listdir({self.inputs['Path'].python_value})"
|
||||
if len(self.outputs) > 1:
|
||||
self.outputs[1].python_value = f"[f for f in os.listdir({self.inputs['Path'].python_value}) if os.path.isfile(os.path.join({self.inputs['Path'].python_value}, f))]"
|
||||
self.outputs[2].python_value = f"[f for f in os.listdir({self.inputs['Path'].python_value}) if os.path.isdir(os.path.join({self.inputs['Path'].python_value}, f))]"
|
||||
|
||||
else:
|
||||
self.outputs[0].python_value = f"[os.path.join({self.inputs['Path'].python_value}, f) for f in os.listdir({self.inputs['Path'].python_value})]"
|
||||
if len(self.outputs) > 1:
|
||||
self.outputs[1].python_value = f"[os.path.join({self.inputs['Path'].python_value}, f) for f in os.listdir({self.inputs['Path'].python_value}) if os.path.isfile(os.path.join({self.inputs['Path'].python_value}, f))]"
|
||||
self.outputs[2].python_value = f"[os.path.join({self.inputs['Path'].python_value}, f) for f in os.listdir({self.inputs['Path'].python_value}) if os.path.isdir(os.path.join({self.inputs['Path'].python_value}, f))]"
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_AbsolutePathNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_AbsolutePathNode"
|
||||
bl_label = "Make Path Absolute"
|
||||
node_color = "STRING"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_string_input("Relative").subtype = "FILE_PATH"
|
||||
self.add_string_output("Absolute").subtype = "FILE_PATH"
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs["Absolute"].python_value = f"bpy.path.abspath({self.inputs[0].python_value})"
|
||||
@@ -0,0 +1,26 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_PathInfoNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_PathInfoNode"
|
||||
bl_label = "Path Info"
|
||||
node_color = "STRING"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_string_input("Path").subtype = "FILE_PATH"
|
||||
self.add_boolean_output("Path Exists")
|
||||
self.add_boolean_output("Is Directory")
|
||||
self.add_string_output("Parent Name")
|
||||
self.add_string_output("Base Name")
|
||||
self.add_string_output("Extension")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_import = "import os"
|
||||
self.outputs['Path Exists'].python_value = f"os.path.exists({self.inputs['Path'].python_value})"
|
||||
self.outputs['Is Directory'].python_value = f"os.path.isdir({self.inputs['Path'].python_value})"
|
||||
self.outputs['Parent Name'].python_value = f"os.path.dirname({self.inputs['Path'].python_value})"
|
||||
self.outputs['Base Name'].python_value = f"os.path.basename({self.inputs['Path'].python_value})"
|
||||
self.outputs['Extension'].python_value = f"os.path.splitext({self.inputs['Path'].python_value})[1]"
|
||||
@@ -0,0 +1,29 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_ClampNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_ClampNode"
|
||||
bl_label = "Clamp"
|
||||
node_color = "FLOAT"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_float_input("Value")
|
||||
self.add_float_input("Min").can_be_disabled = True
|
||||
self.add_float_input("Max").can_be_disabled = True
|
||||
self.add_float_output("Float Result")
|
||||
self.add_integer_output("Integer Result")
|
||||
|
||||
def evaluate(self, context):
|
||||
smallest = self.inputs["Min"].python_value
|
||||
largest = self.inputs["Max"].python_value
|
||||
|
||||
if self.inputs["Min"].disabled:
|
||||
smallest = self.inputs["Value"].python_value
|
||||
if self.inputs["Max"].disabled:
|
||||
largest = self.inputs["Value"].python_value
|
||||
|
||||
self.outputs[0].python_value = f"float(max({smallest}, min({self.inputs[0].python_value}, {largest})))"
|
||||
self.outputs[1].python_value = f"int(max({smallest}, min({self.inputs[0].python_value}, {largest})))"
|
||||
@@ -0,0 +1,74 @@
|
||||
import re
|
||||
import bpy
|
||||
import string
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_MathNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_MathNode"
|
||||
bl_label = "Math"
|
||||
node_color = "FLOAT"
|
||||
|
||||
def on_dynamic_socket_add(self, socket):
|
||||
alphabet = list(string.ascii_lowercase)
|
||||
if len(self.inputs) > 26:
|
||||
self.inputs.remove(socket)
|
||||
for x, socket in enumerate(self.inputs):
|
||||
socket.name = alphabet[x]
|
||||
|
||||
def on_dynamic_socket_remove(self, index, is_output):
|
||||
alphabet = list(string.ascii_lowercase)
|
||||
if self.inputs[-2].name != "z" and self.inputs[-1].hide:
|
||||
self.inputs[-1].set_hide(False)
|
||||
if self.inputs[-2].name != "z":
|
||||
self.inputs[-1].name = alphabet[alphabet.index(self.inputs[-2].name)+1]
|
||||
|
||||
operation: bpy.props.EnumProperty(items=[(" + ", "Add", "Add two numbers"),
|
||||
(" - ", "Subtract", "Subtract two numbers"),
|
||||
(" * ", "Multiply", "Multiply two numbers"),
|
||||
(" / ", "Divide", "Divide two numbers"),
|
||||
("EXPRESSION","Expression","Enter your own expression")],
|
||||
name="Operation",
|
||||
description="Operation to perform on the input data",
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
expression: bpy.props.StringProperty(default="a + b", update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_float_input("a")
|
||||
self.add_float_input("b")
|
||||
self.add_dynamic_float_input("c")
|
||||
self.add_float_output("Float Result")
|
||||
self.add_integer_output("Integer Result")
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "operation", text="")
|
||||
if self.operation == "EXPRESSION":
|
||||
layout.prop(self,"expression",text="")
|
||||
|
||||
def multiple_replace(self, string, rep_dict):
|
||||
for key, value in rep_dict.items():
|
||||
string = re.sub(rf'\b{key}\b', value, string)
|
||||
return string
|
||||
|
||||
def evaluate(self, context):
|
||||
if not self.operation == "EXPRESSION":
|
||||
values = [inp.python_value for inp in self.inputs[:-1]]
|
||||
self.outputs[0].python_value = f"float({self.operation.join(values)})"
|
||||
self.outputs[1].python_value = f"int({self.operation.join(values)})"
|
||||
|
||||
else:
|
||||
self.code_import = "import math"
|
||||
expression = self.expression
|
||||
|
||||
to_replace = {}
|
||||
for inp in self.inputs:
|
||||
if not inp.dynamic:
|
||||
to_replace[inp.name] = inp.python_value
|
||||
|
||||
expression = self.multiple_replace(expression, to_replace)
|
||||
|
||||
self.outputs[0].python_value = f"eval(\"{expression}\")"
|
||||
self.outputs[1].python_value = f"int(eval(\"{expression}\"))"
|
||||
@@ -0,0 +1,18 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_RoundNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_RoundNode"
|
||||
bl_label = "Round Number"
|
||||
node_color = "FLOAT"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_float_input("Value")
|
||||
self.add_integer_input("Decimals")
|
||||
self.add_float_output("Rounded Number")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs[0].python_value = f"round({self.inputs['Value'].python_value}, abs({self.inputs['Decimals'].python_value}))"
|
||||
@@ -0,0 +1,81 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_VectorMathNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_VectorMathNode"
|
||||
bl_label = "Vector Math"
|
||||
node_color = "VECTOR"
|
||||
|
||||
def update_operation(self,context):
|
||||
if self.operation == "CROSS_PRODUCT":
|
||||
self.size = 3
|
||||
|
||||
if self.operation == "LENGTH":
|
||||
if len(self.inputs) == 2:
|
||||
self.inputs.remove(self.inputs[1])
|
||||
self.convert_socket(self.outputs[0], self.socket_names["Float"])
|
||||
else:
|
||||
if len(self.inputs) == 1:
|
||||
self.add_float_vector_input("B").size = self.size
|
||||
self.convert_socket(self.outputs[0], self.socket_names["Float Vector"])
|
||||
|
||||
if self.operation in ["DIVIDE"]:
|
||||
self.convert_socket(self.inputs[1], self.socket_names["Float"])
|
||||
elif len(self.inputs) > 1:
|
||||
self.convert_socket(self.inputs[1], self.socket_names["Float Vector"])
|
||||
|
||||
self._evaluate(context)
|
||||
|
||||
|
||||
def update_size(self,context):
|
||||
if self.operation == "CROSS_PRODUCT":
|
||||
self["size"] = 3
|
||||
self.inputs[0].size = self.size
|
||||
self.inputs[1].size = self.size
|
||||
self._evaluate(context)
|
||||
|
||||
size: bpy.props.IntProperty(default=3, min=2, max=32,
|
||||
name="Size",
|
||||
description="Size of this the vector",
|
||||
update=update_size)
|
||||
|
||||
operation: bpy.props.EnumProperty(items=[("ADD", "Add", "Add two vectors"),
|
||||
("SUBTRACT", "Subtract", "Subtract two vectors"),
|
||||
("MULTIPLY", "Multiply", "Multiply two vectors"),
|
||||
("DIVIDE", "Divide", "Divide vector by float"),
|
||||
("CROSS_PRODUCT", "Cross Product", "Cross Product of two vectors"),
|
||||
("DOT_PRODUCT", "Dot Product", "Dot Product of two vectors"),
|
||||
("LENGTH", "Length", "Length of a vector")],
|
||||
name="Operation", description="The operation you want to commence",
|
||||
update=update_operation)
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_float_vector_input("A")
|
||||
self.add_float_vector_input("B")
|
||||
self.add_float_vector_output("Vector")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.code_import = "import mathutils"
|
||||
if self.operation == "ADD":
|
||||
self.outputs[0].python_value = f"tuple(mathutils.Vector({self.inputs[0].python_value}) + mathutils.Vector({self.inputs[1].python_value}))"
|
||||
elif self.operation == "SUBTRACT":
|
||||
self.outputs[0].python_value = f"tuple(mathutils.Vector({self.inputs[0].python_value}) - mathutils.Vector({self.inputs[1].python_value}))"
|
||||
elif self.operation == "MULTIPLY":
|
||||
self.outputs[0].python_value = f"tuple(mathutils.Vector({self.inputs[0].python_value}) * mathutils.Vector({self.inputs[1].python_value}))"
|
||||
elif self.operation == "DIVIDE":
|
||||
self.outputs[0].python_value = f"tuple(mathutils.Vector({self.inputs[0].python_value}) / {self.inputs[1].python_value})"
|
||||
elif self.operation == "CROSS_PRODUCT":
|
||||
self.outputs[0].python_value = f"tuple(mathutils.Vector({self.inputs[0].python_value}).cross(mathutils.Vector({self.inputs[1].python_value})))"
|
||||
elif self.operation == "DOT_PRODUCT":
|
||||
self.outputs[0].python_value = f"mathutils.Vector({self.inputs[0].python_value}).dot(mathutils.Vector({self.inputs[1].python_value}))"
|
||||
elif self.operation == "LENGTH":
|
||||
self.outputs[0].python_value = f"mathutils.Vector({self.inputs[0].python_value}).length"
|
||||
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "operation", text="")
|
||||
if self.operation != "CROSS_PRODUCT":
|
||||
layout.prop(self,"size")
|
||||
@@ -0,0 +1,66 @@
|
||||
import bpy
|
||||
from random import randint
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
class SN_OT_RandomIndex(bpy.types.Operator):
|
||||
bl_idname = "sn.random_index"
|
||||
bl_label = "New Random"
|
||||
bl_description = "Shows you a new random item"
|
||||
bl_options = {"REGISTER","UNDO","INTERNAL"}
|
||||
|
||||
max_value: bpy.props.IntProperty()
|
||||
node: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
node = context.space_data.node_tree.nodes[self.node]
|
||||
node.index = randint(0,self.max_value)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SN_LofiNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_LofiNode"
|
||||
bl_label = "LoFi"
|
||||
bl_width_default = 300
|
||||
|
||||
links = [
|
||||
("Potsu Mix", "https://www.youtube.com/watch?v=FSnuF1FPSIU&list=PLp7HpIyLajPJM1np34q3Sad8DjDq2gkJd&ab_channel=potsu-Topic"),
|
||||
("Relax/Study", "https://www.youtube.com/watch?v=5qap5aO4i9A&ab_channel=ChilledCow"),
|
||||
("Sleep/Chill", "https://www.youtube.com/watch?v=DWcJFNfaw9c&ab_channel=ChilledCow"),
|
||||
("Chillhop Radio", "https://www.youtube.com/watch?v=5yx6BWlEVcY&ab_channel=ChillhopMusic"),
|
||||
("LoFi HipHop", "https://www.youtube.com/watch?v=0te6noMKffA&ab_channel=Netizens"),
|
||||
("StarWars LoFi", "https://www.youtube.com/watch?v=78cyA-aGaKc&t=108s&ab_channel=Amemos-Ambience"),
|
||||
("Samurai", "https://www.youtube.com/watch?v=jrTMMG0zJyI&t=9s&ab_channel=thebootlegboy"),
|
||||
("Code-Fi", "https://www.youtube.com/watch?v=f02mOEt11OQ&ab_channel=TheAMPChannel"),
|
||||
("LoFi Jazz", "https://www.youtube.com/watch?v=esX7SFtEjHg&ab_channel=CodePioneers"),
|
||||
("Minecraft LoFi", "https://www.youtube.com/watch?v=hy0bAbznU6g&ab_channel=SolivagantSounds"),
|
||||
("Late Night Nostalgia", "https://www.youtube.com/watch?v=dLmyp3xMsAo&ab_channel=DreamhopMusic"),
|
||||
("Coffee Shop Radio", "https://www.youtube.com/watch?v=-5KAN9_CzSA&ab_channel=STEEZYASFUCK"),
|
||||
("Old Songs LoFi", "https://www.youtube.com/watch?v=bPPiuludHKg&ab_channel=Lo-fiMusic"),
|
||||
("Mind On Clouds", "https://www.youtube.com/watch?v=Hdncb04CdWw&ab_channel=TunableMusic"),
|
||||
("Chill Beats", "https://www.youtube.com/watch?v=rA56B4JyTgI&ab_channel=WillSmith"),
|
||||
("ibr Beats", "https://soundcloud.com/i-b-r/popular-tracks")
|
||||
]
|
||||
|
||||
index: bpy.props.IntProperty(default=0)
|
||||
|
||||
def on_create(self, context):
|
||||
self.index = randint(0,len(self.links)-1)
|
||||
self.color = (0.184, 0.184, 0.184)
|
||||
|
||||
def on_copy(self,node):
|
||||
self.index = randint(0,len(self.links)-1)
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
box = layout.box()
|
||||
col = box.column()
|
||||
col.label(text="This node is purely for your enjoyment!",icon="FUND")
|
||||
col.label(text="Have a really nice day and enjoy the music!",icon="BLANK1")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.scale_y = 1.5
|
||||
row.operator("wm.url_open",text=self.links[self.index][0],depress=True,icon="FILE_SOUND").url = self.links[self.index][1]
|
||||
op = row.operator("sn.random_index",text="",icon="FILE_REFRESH",depress=True)
|
||||
op.node = self.name
|
||||
op.max_value = len(self.links)-1
|
||||
@@ -0,0 +1,19 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_SwitchDataNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_SwitchDataNode"
|
||||
bl_label = "Switch Data"
|
||||
node_color = "DEFAULT"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_boolean_input("Switch")
|
||||
self.add_data_input("Data 1")
|
||||
self.add_data_input("Data 2")
|
||||
self.add_data_output("Data").changeable = True
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs[0].python_value = f"({self.inputs[2].python_value} if {self.inputs[0].python_value} else {self.inputs[1].python_value})"
|
||||
@@ -0,0 +1,19 @@
|
||||
import bpy
|
||||
from ..base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_SwitchIconNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_SwitchIconNode"
|
||||
bl_label = "Switch Icons"
|
||||
node_color = "ICON"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_boolean_input("Switch")
|
||||
self.add_icon_input("Icon 1")
|
||||
self.add_icon_input("Icon 2")
|
||||
self.add_icon_output("Icon")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs[0].python_value = f"({self.inputs[2].python_value} if {self.inputs[0].python_value} else {self.inputs[1].python_value})"
|
||||
@@ -0,0 +1,20 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_CombineStringsNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_CombineStringsNode"
|
||||
bl_label = "Combine Strings"
|
||||
node_color = "STRING"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_string_input("String").prev_dynamic = True
|
||||
self.add_dynamic_string_input("String")
|
||||
self.add_string_output("Combined String")
|
||||
|
||||
def evaluate(self, context):
|
||||
values = [" + " + inp.python_value for inp in self.inputs[1:-1]]
|
||||
self.outputs["Combined String"].python_value = self.inputs[0].python_value + "".join(values)
|
||||
@@ -0,0 +1,17 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_DecodeStringNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_DecodeStringNode"
|
||||
bl_label = "Decode Byte String"
|
||||
node_color = "STRING"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_string_input("Bytes")
|
||||
self.add_string_output("String")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs[0].python_value = f"{self.inputs['Bytes'].python_value}.decode('utf-8') "
|
||||
@@ -0,0 +1,17 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_EncodeStringNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_EncodeStringNode"
|
||||
bl_label = "Encode Byte String"
|
||||
node_color = "STRING"
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_string_input("String")
|
||||
self.add_string_output("Bytes")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs[0].python_value = f"{self.inputs['String'].python_value}.encode() "
|
||||
@@ -0,0 +1,19 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_IsInStringNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_IsInStringNode"
|
||||
bl_label = "Substring is in String"
|
||||
node_color = "STRING"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_string_input("String")
|
||||
self.add_string_input("Substring")
|
||||
self.add_boolean_output("Is in String")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs[0].python_value = f"{self.inputs['Substring'].python_value} in {self.inputs['String'].python_value}"
|
||||
@@ -0,0 +1,19 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_JoinStringsNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_JoinStringsNode"
|
||||
bl_label = "Join Strings"
|
||||
node_color = "STRING"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_list_input("String List")
|
||||
self.add_string_input("Join On")
|
||||
self.add_string_output("String")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs["String"].python_value = f"{self.inputs['Join On'].python_value}.join({self.inputs['String List'].python_value})"
|
||||
@@ -0,0 +1,54 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class StringMap(bpy.types.PropertyGroup):
|
||||
|
||||
def update_item(self, context):
|
||||
for node in self.id_data.node_collection("SN_MapStringsNode").nodes:
|
||||
for item in node.map_collection:
|
||||
if item == self:
|
||||
node._evaluate(context)
|
||||
return
|
||||
|
||||
name: bpy.props.StringProperty(name="From", description="The value from which to map", update=update_item)
|
||||
to_string: bpy.props.StringProperty(name="To", description="The value to which to map", update=update_item)
|
||||
|
||||
|
||||
|
||||
class SN_MapStringsNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_MapStringsNode"
|
||||
bl_label = "Map Strings"
|
||||
node_color = "STRING"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_string_input("Input")
|
||||
self.add_string_output("Mapped")
|
||||
|
||||
map_collection: bpy.props.CollectionProperty(type=StringMap)
|
||||
|
||||
def evaluate(self, context):
|
||||
lookup = "{"
|
||||
for item in self.map_collection:
|
||||
lookup += f"'{item.name}': '{item.to_string}', "
|
||||
lookup += "}"
|
||||
self.outputs[0].python_value = f"{lookup}[{self.inputs[0].python_value}]"
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
op = layout.operator("sn.add_string_map_item", icon="ADD")
|
||||
op.node_tree = self.node_tree.name
|
||||
op.node = self.name
|
||||
|
||||
col = layout.column(align=True)
|
||||
for i, item in enumerate(self.map_collection):
|
||||
box = col.box()
|
||||
row = box.row()
|
||||
row.prop(item, "name")
|
||||
op = row.operator("sn.remove_string_map_item", text="", icon="PANEL_CLOSE", emboss=False)
|
||||
op.node_tree = self.node_tree.name
|
||||
op.node = self.name
|
||||
op.index = i
|
||||
box.prop(item, "to_string")
|
||||
@@ -0,0 +1,20 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_PadStringNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_PadStringNode"
|
||||
bl_label = "Pad String"
|
||||
node_color = "STRING"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_string_input("String")
|
||||
self.add_integer_input("Size")
|
||||
self.add_string_input("Pad")
|
||||
self.add_string_output("Padded String")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs["Padded String"].python_value = f"{self.inputs['String'].python_value}.rjust({self.inputs['Size'].python_value}, {self.inputs['Pad'].python_value})"
|
||||
@@ -0,0 +1,20 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_ReplaceStringNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_ReplaceStringNode"
|
||||
bl_label = "Replace in String"
|
||||
node_color = "STRING"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_string_input("String")
|
||||
self.add_string_input("Old")
|
||||
self.add_string_input("New")
|
||||
self.add_string_output("String")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs[0].python_value = f"{self.inputs[0].python_value}.replace({self.inputs[1].python_value}, {self.inputs[2].python_value})"
|
||||
@@ -0,0 +1,21 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_SliceStringNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_SliceStringNode"
|
||||
bl_label = "Slice String"
|
||||
node_color = "STRING"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_string_input("String")
|
||||
self.add_integer_input("Index")
|
||||
self.add_string_output("Before")
|
||||
self.add_string_output("After")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs["Before"].python_value = f"{self.inputs['String'].python_value}[:{self.inputs['Index'].python_value}]"
|
||||
self.outputs["After"].python_value = f"{self.inputs['String'].python_value}[{self.inputs['Index'].python_value}:]"
|
||||
@@ -0,0 +1,19 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_SplitStringNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_SplitStringNode"
|
||||
bl_label = "Split String"
|
||||
node_color = "STRING"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_string_input("String")
|
||||
self.add_string_input("Split On")
|
||||
self.add_list_output("Split List")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs["Split List"].python_value = f"{self.inputs['String'].python_value}.split({self.inputs['Split On'].python_value})"
|
||||
@@ -0,0 +1,18 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_StringLengthNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_StringLengthNode"
|
||||
bl_label = "String Length"
|
||||
node_color = "STRING"
|
||||
bl_width_default = 200
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_string_input("String")
|
||||
self.add_integer_output("Length")
|
||||
|
||||
def evaluate(self, context):
|
||||
self.outputs["Length"].python_value = f"len({self.inputs[0].python_value})"
|
||||
@@ -0,0 +1,33 @@
|
||||
import bpy
|
||||
from ...base_node import SN_ScriptingBaseNode
|
||||
|
||||
|
||||
|
||||
class SN_StripStringNode(SN_ScriptingBaseNode, bpy.types.Node):
|
||||
|
||||
bl_idname = "SN_StripStringNode"
|
||||
bl_label = "Strip String"
|
||||
node_color = "STRING"
|
||||
bl_width_default = 200
|
||||
|
||||
position: bpy.props.EnumProperty(name="Position",
|
||||
description="Sides of the string to strip",
|
||||
items=[("BOTH", "Both", "Remove whitespace on both sides of the string"),
|
||||
("LEFT", "Left", "Remove whitespace on the left side of the string"),
|
||||
("RIGHT", "Right", "Remove whitespace on the right side of the string")],
|
||||
update=SN_ScriptingBaseNode._evaluate)
|
||||
|
||||
def on_create(self, context):
|
||||
self.add_string_input("String")
|
||||
self.add_string_output("Stripped String")
|
||||
|
||||
def evaluate(self, context):
|
||||
if self.position == "BOTH":
|
||||
self.outputs["Stripped String"].python_value = f"{self.inputs['String'].python_value}.strip()"
|
||||
elif self.position == "LEFT":
|
||||
self.outputs["Stripped String"].python_value = f"{self.inputs['String'].python_value}.lstrip()"
|
||||
elif self.position == "RIGHT":
|
||||
self.outputs["Stripped String"].python_value = f"{self.inputs['String'].python_value}.rstrip()"
|
||||
|
||||
def draw_node(self, context, layout):
|
||||
layout.prop(self, "position", expand=True)
|
||||
@@ -0,0 +1,36 @@
|
||||
import bpy
|
||||
|
||||
|
||||
|
||||
class SN_OT_AddStringMapItem(bpy.types.Operator):
|
||||
bl_idname = "sn.add_string_map_item"
|
||||
bl_label = "Add Item"
|
||||
bl_description = "Adds an item to this node"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
node_tree: bpy.props.StringProperty(options={"HIDDEN", "SKIP_SAVE"})
|
||||
node: bpy.props.StringProperty(options={"HIDDEN", "SKIP_SAVE"})
|
||||
|
||||
def execute(self, context):
|
||||
node = bpy.data.node_groups[self.node_tree].nodes[self.node]
|
||||
node.map_collection.add()
|
||||
node._evaluate(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
class SN_OT_RemoveStringMapItem(bpy.types.Operator):
|
||||
bl_idname = "sn.remove_string_map_item"
|
||||
bl_label = "Remove Item"
|
||||
bl_description = "Removes an item from this node"
|
||||
bl_options = {"REGISTER", "INTERNAL"}
|
||||
|
||||
node: bpy.props.StringProperty(options={"HIDDEN", "SKIP_SAVE"})
|
||||
node_tree: bpy.props.StringProperty(options={"HIDDEN", "SKIP_SAVE"})
|
||||
index: bpy.props.IntProperty(options={"HIDDEN", "SKIP_SAVE"})
|
||||
|
||||
def execute(self, context):
|
||||
node = bpy.data.node_groups[self.node_tree].nodes[self.node]
|
||||
node.map_collection.remove(self.index)
|
||||
node._evaluate(context)
|
||||
return {"FINISHED"}
|
||||
Reference in New Issue
Block a user