mirror of
https://github.com/H-uru/korman.git
synced 2025-07-14 22:36:52 +00:00
Begin work on PythonFileMod nodes
This has some simple attribute nodes for example purposes. Unfortunately, this is so far UI only. More node types need to be added, then we can begin working on exporting.
This commit is contained in:
@ -23,6 +23,7 @@ from .node_avatar import *
|
||||
from .node_conditions import *
|
||||
from .node_core import *
|
||||
from .node_messages import *
|
||||
from .node_python import *
|
||||
from .node_responder import *
|
||||
|
||||
class PlasmaNodeCategory(NodeCategory):
|
||||
@ -42,6 +43,7 @@ _kategory_names = {
|
||||
"CONDITIONS": "Conditions",
|
||||
"LOGIC": "Logic",
|
||||
"MSG": "Message",
|
||||
"PYTHON": "Python",
|
||||
}
|
||||
|
||||
# Now, generate the categories as best we can...
|
||||
|
@ -135,6 +135,7 @@ class PlasmaClickableRegionNode(PlasmaNodeBase, bpy.types.Node):
|
||||
default="hull")
|
||||
|
||||
def init(self, context):
|
||||
self.outputs.new("PlasmaPythonReferenceNodeSocket", "References", "keyref")
|
||||
self.outputs.new("PlasmaClickableRegionSocket", "Satisfies", "satisfies")
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
|
@ -123,6 +123,11 @@ class PlasmaNodeBase:
|
||||
out_socket = out_key
|
||||
link = self.id_data.links.new(in_socket, out_socket)
|
||||
|
||||
@property
|
||||
def node_path(self):
|
||||
"""Returns an absolute path to this Node. Needed because repr() uses an elipsis..."""
|
||||
return "{}.{}".format(repr(self.id_data), self.path_from_id())
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return (context.bl_idname == "PlasmaNodeTree")
|
||||
|
314
korman/nodes/node_python.py
Normal file
314
korman/nodes/node_python.py
Normal file
@ -0,0 +1,314 @@
|
||||
# This file is part of Korman.
|
||||
#
|
||||
# Korman is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Korman is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Korman. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from bpy.props import *
|
||||
import os.path
|
||||
from PyHSPlasma import *
|
||||
|
||||
from .node_core import *
|
||||
|
||||
_attrib_colors = {
|
||||
"ptAttribActivator": (0.451, 0.0, 0.263, 1.0),
|
||||
"ptAttribActivatorList": (0.451, 0.0, 0.263, 1.0),
|
||||
"ptAttribBoolean": (0.71, 0.706, 0.655, 1.0),
|
||||
"ptAttribFloat": (0.443, 0.439, 0.392, 1.0),
|
||||
("ptAttribFloat", "ptAttribInt"): (0.443, 0.439, 0.392, 1.0),
|
||||
"ptAttribInt": (0.443, 0.439, 0.392, 1.0),
|
||||
"ptAttribResponder": (0.031, 0.110, 0.290, 1.0),
|
||||
"ptAttribResponderList": (0.031, 0.110, 0.290, 1.0),
|
||||
"ptAttribString": (0.675, 0.659, 0.494, 1.0),
|
||||
}
|
||||
|
||||
_single_user_attribs = {
|
||||
"ptAttribBoolean", "ptAttribInt", "ptAttribFloat", "ptAttribString", "ptAttribDropDownList",
|
||||
"ptAttribSceneobject", "ptAttribDynamicMap", "ptAttribGUIDialog", "ptAttribExcludeRegion",
|
||||
"ptAttribWaveSet", "ptAttribSwimCurrent", "ptAttribAnimation", "ptAttribBehavior",
|
||||
"ptAttribMaterial", "ptAttribMaterialAnimation", "ptAttribGUIPopUpMenu", "ptAttribGUISkin",
|
||||
"ptAttribGrassShader",
|
||||
}
|
||||
|
||||
class PlasmaAttribute(bpy.types.PropertyGroup):
|
||||
attribute_id = IntProperty()
|
||||
attribute_type = StringProperty()
|
||||
attribute_name = StringProperty()
|
||||
attribute_description = StringProperty()
|
||||
|
||||
# These shall be default values
|
||||
value_string = StringProperty()
|
||||
value_int = IntProperty()
|
||||
value_float = FloatProperty()
|
||||
value_bool = BoolProperty()
|
||||
|
||||
_simple_attrs = {
|
||||
"ptAttribString": "value_string",
|
||||
"ptAttribInt": "value_int",
|
||||
"ptAttribFloat": "value_float",
|
||||
"ptAttribBoolean": "value_bool",
|
||||
}
|
||||
|
||||
@property
|
||||
def is_simple_value(self):
|
||||
return self.attribute_type in self._simple_attrs
|
||||
|
||||
def _get_simple_value(self):
|
||||
return getattr(self, self._simple_attrs[self.attribute_type])
|
||||
def _set_simple_value(self, value):
|
||||
setattr(self, self._simple_attrs[self.attribute_type], value)
|
||||
simple_value = property(_get_simple_value, _set_simple_value)
|
||||
|
||||
|
||||
class PlasmaPythonFileNode(PlasmaNodeBase, bpy.types.Node):
|
||||
bl_category = "PYTHON"
|
||||
bl_idname = "PlasmaPythonFileNode"
|
||||
bl_label = "Python File"
|
||||
bl_width_default = 210
|
||||
|
||||
def _update_pyfile(self, context):
|
||||
# Changing the file path? let's start anew.
|
||||
self.attributes.clear()
|
||||
self.inputs.clear()
|
||||
|
||||
# Now populate that BAMF
|
||||
bpy.ops.node.plasma_attributes_to_node(node_path=self.node_path, python_path=self.filepath)
|
||||
|
||||
filename = StringProperty(name="File",
|
||||
description="Python Filename")
|
||||
filepath = StringProperty(update=_update_pyfile,
|
||||
options={"HIDDEN"})
|
||||
|
||||
attributes = CollectionProperty(type=PlasmaAttribute, options={"HIDDEN"})
|
||||
dirty_attributes = BoolProperty(options={"HIDDEN"})
|
||||
|
||||
@property
|
||||
def attribute_map(self):
|
||||
return { i.attribute_id: i for i in self.attributes }
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
row = layout.row(align=True)
|
||||
if self.filename:
|
||||
row.prop(self, "filename")
|
||||
operator = row.operator("node.plasma_attributes_to_node", icon="FILE_REFRESH", text="")
|
||||
operator.python_path = self.filepath
|
||||
operator.node_path = self.node_path
|
||||
|
||||
op_text = "" if self.filename else "Select"
|
||||
operator = row.operator("file.plasma_file_picker", icon="SCRIPT", text=op_text)
|
||||
operator.filter_glob = "*.py"
|
||||
operator.data_path = self.node_path
|
||||
operator.filepath_property = "filepath"
|
||||
operator.filename_property = "filename"
|
||||
|
||||
def _get_attrib_sockets(self, idx):
|
||||
for i in self.inputs:
|
||||
if i.attribute_id == idx:
|
||||
yield i
|
||||
|
||||
def _make_attrib_socket(self, attrib, is_init=False):
|
||||
new_pos = len(self.inputs)
|
||||
if not is_init:
|
||||
for i, socket in enumerate(self.inputs):
|
||||
if attrib.attribute_id < socket.attribute_id:
|
||||
new_pos = i
|
||||
break
|
||||
old_pos = len(self.inputs)
|
||||
socket = self.inputs.new("PlasmaPythonFileNodeSocket", "", "")
|
||||
socket.attribute_id = attrib.attribute_id
|
||||
if not is_init and new_pos != old_pos:
|
||||
self.inputs.move(old_pos, new_pos)
|
||||
|
||||
def update(self):
|
||||
attribs = self.attribute_map
|
||||
empty = not self.inputs
|
||||
for idx in sorted(attribs):
|
||||
attrib = attribs[idx]
|
||||
|
||||
# Delete any attribute sockets whose type changed
|
||||
for i in self._get_attrib_sockets(attrib.attribute_id):
|
||||
if i.attribute_type != attrib.attribute_type:
|
||||
self.inputs.remove(i)
|
||||
|
||||
# Fetch the list of sockets again because we may have nuked some
|
||||
inputs = list(self._get_attrib_sockets(attrib.attribute_id))
|
||||
if not inputs:
|
||||
self._make_attrib_socket(attrib, empty)
|
||||
elif attrib.attribute_type not in _single_user_attribs:
|
||||
unconnected = [socket for socket in inputs if not socket.is_linked]
|
||||
if not unconnected:
|
||||
self._make_attrib_socket(attrib, empty)
|
||||
while len(unconnected) > 1:
|
||||
self.inputs.remove(unconnected.pop())
|
||||
|
||||
|
||||
class PlasmaPythonFileNodeSocket(bpy.types.NodeSocket):
|
||||
attribute_id = IntProperty(options={"HIDDEN"})
|
||||
|
||||
@property
|
||||
def attribute_description(self):
|
||||
return self.node.attribute_map[self.attribute_id].attribute_description
|
||||
|
||||
@property
|
||||
def attribute_name(self):
|
||||
return self.node.attribute_map[self.attribute_id].attribute_name
|
||||
|
||||
@property
|
||||
def attribute_type(self):
|
||||
return self.node.attribute_map[self.attribute_id].attribute_type
|
||||
|
||||
def draw(self, context, layout, node, text):
|
||||
layout.alignment = "LEFT"
|
||||
layout.label("ID: {}".format(self.attribute_id))
|
||||
layout.label(self.attribute_description)
|
||||
|
||||
def draw_color(self, context, node):
|
||||
return _attrib_colors.get(self.attribute_type, (0.0, 0.0, 0.0, 1.0))
|
||||
|
||||
@property
|
||||
def is_simple_value(self):
|
||||
return self.node.attribute_map[self.attribute_id].is_simple_value
|
||||
|
||||
@property
|
||||
def simple_value(self):
|
||||
return self.node.attribute_map[self.attribute_id].simple_value
|
||||
|
||||
|
||||
class PlasmaPythonAttribNodeSocket(bpy.types.NodeSocket):
|
||||
def draw(self, context, layout, node, text):
|
||||
attrib = node.to_socket
|
||||
if attrib is None:
|
||||
layout.label(text)
|
||||
else:
|
||||
layout.label("ID: {}".format(attrib.attribute_id))
|
||||
|
||||
def draw_color(self, context, node):
|
||||
return _attrib_colors.get(node.pl_attrib, (0.0, 0.0, 0.0, 1.0))
|
||||
|
||||
|
||||
class PlasmaPythonReferenceNodeSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
|
||||
bl_color = (0.031, 0.110, 0.290, 1.0)
|
||||
|
||||
|
||||
class PlasmaAttribNodeBase(PlasmaNodeBase):
|
||||
def init(self, context):
|
||||
self.outputs.new("PlasmaPythonAttribNodeSocket", "Python File", "pfm")
|
||||
|
||||
@property
|
||||
def attribute_name(self):
|
||||
attr = self.to_socket
|
||||
return "Value" if attr is None else attr.attribute_name
|
||||
|
||||
@property
|
||||
def to_socket(self):
|
||||
"""Returns the socket linked to IF only one link has been made"""
|
||||
socket = self.outputs[0]
|
||||
if len(socket.links) == 1:
|
||||
return socket.links[0].to_socket
|
||||
return None
|
||||
|
||||
def update(self):
|
||||
pl_id = self.pl_attrib
|
||||
socket = self.outputs[0]
|
||||
for link in socket.links:
|
||||
if link.to_node.bl_idname != "PlasmaPythonFileNode":
|
||||
self.id_data.links.remove(link)
|
||||
if isinstance(pl_id, tuple):
|
||||
if link.to_socket.attribute_type not in pl_id:
|
||||
self.id_data.links.remove(link)
|
||||
else:
|
||||
if pl_id != link.to_socket.attribute_type:
|
||||
self.id_data.links.remove(link)
|
||||
|
||||
|
||||
class PlasmaAttribBoolNode(PlasmaAttribNodeBase, bpy.types.Node):
|
||||
bl_category = "PYTHON"
|
||||
bl_idname = "PlasmaAttribBoolNode"
|
||||
bl_label = "Boolean Attribute"
|
||||
|
||||
def _on_update(self, context):
|
||||
self.inited = True
|
||||
|
||||
pl_attrib = "ptAttribBoolean"
|
||||
value = BoolProperty()
|
||||
inited = BoolProperty(options={"HIDDEN"})
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
layout.prop(self, "value", text=self.attribute_name)
|
||||
|
||||
def update(self):
|
||||
super().update()
|
||||
attrib = self.to_socket
|
||||
if attrib is not None and not self.inited:
|
||||
self.value = attrib.simple_value
|
||||
self.inited = True
|
||||
|
||||
|
||||
class PlasmaAttribNumericNode(PlasmaAttribNodeBase, bpy.types.Node):
|
||||
bl_category = "PYTHON"
|
||||
bl_idname = "PlasmaAttribIntNode"
|
||||
bl_label = "Numeric Attribute"
|
||||
|
||||
def _on_update_int(self, context):
|
||||
self.value_float = float(self.value_int)
|
||||
self.inited = True
|
||||
|
||||
def _on_update_float(self, context):
|
||||
self.value_int = int(self.value_float)
|
||||
self.inited = True
|
||||
|
||||
pl_attrib = ("ptAttribFloat", "ptAttribInt")
|
||||
value_int = IntProperty(update=_on_update_int, options={"HIDDEN"})
|
||||
value_float = FloatProperty(update=_on_update_float, options={"HIDDEN"})
|
||||
inited = BoolProperty(options={"HIDDEN"})
|
||||
|
||||
def init(self, context):
|
||||
super().init(context)
|
||||
# because we're trying to be for both int and float...
|
||||
self.outputs[0].link_limit = 1
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
attrib = self.to_socket
|
||||
if attrib is None:
|
||||
layout.prop(self, "value_int", text="Value")
|
||||
elif attrib.attribute_type == "ptAttribFloat":
|
||||
layout.prop(self, "value_float", text=attrib.name)
|
||||
elif attrib.attribute_type == "ptAttribInt":
|
||||
layout.prop(self, "value_int", text=attrib.name)
|
||||
else:
|
||||
raise RuntimeError()
|
||||
|
||||
def update(self):
|
||||
super().update()
|
||||
attrib = self.to_socket
|
||||
if attrib is not None and not self.inited:
|
||||
self.value = attrib.simple_value
|
||||
self.inited = True
|
||||
|
||||
|
||||
class PlasmaAttribStringNode(PlasmaAttribNodeBase, bpy.types.Node):
|
||||
bl_category = "PYTHON"
|
||||
bl_idname = "PlasmaAttribStringNode"
|
||||
bl_label = "String Attribute"
|
||||
|
||||
pl_attrib = "ptAttribString"
|
||||
value = StringProperty()
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
layout.prop(self, "value", text=self.attribute_name)
|
||||
|
||||
def update(self):
|
||||
super().update()
|
||||
attrib = self.to_socket
|
||||
if attrib is not None:
|
||||
self.value = attrib.simple_value
|
@ -38,6 +38,7 @@ class PlasmaResponderNode(PlasmaNodeVariableInput, bpy.types.Node):
|
||||
|
||||
def init(self, context):
|
||||
self.inputs.new("PlasmaConditionSocket", "Condition", "condition")
|
||||
self.outputs.new("PlasmaPythonReferenceNodeSocket", "References", "keyref")
|
||||
self.outputs.new("PlasmaRespStateSocket", "States", "states")
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
|
@ -16,6 +16,7 @@
|
||||
from . import op_export as exporter
|
||||
from . import op_lightmap as lightmap
|
||||
from . import op_modifier as modifier
|
||||
from . import op_nodes as nodes
|
||||
from . import op_toolbox as toolbox
|
||||
from . import op_world as world
|
||||
|
||||
|
87
korman/operators/op_nodes.py
Normal file
87
korman/operators/op_nodes.py
Normal file
@ -0,0 +1,87 @@
|
||||
# This file is part of Korman.
|
||||
#
|
||||
# Korman is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Korman is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Korman. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from bpy.props import *
|
||||
import itertools
|
||||
|
||||
class NodeOperator:
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.scene.render.engine == "PLASMA_GAME"
|
||||
|
||||
|
||||
class SelectFileOperator(NodeOperator, bpy.types.Operator):
|
||||
bl_idname = "file.plasma_file_picker"
|
||||
bl_label = "Select"
|
||||
|
||||
filter_glob = StringProperty(options={"HIDDEN"})
|
||||
filepath = StringProperty(subtype="FILE_PATH")
|
||||
filename = StringProperty(options={"HIDDEN"})
|
||||
|
||||
data_path = StringProperty(options={"HIDDEN"})
|
||||
filepath_property = StringProperty(description="Name of property to store filepath in", options={"HIDDEN"})
|
||||
filename_property = StringProperty(description="Name of property to store filename in", options={"HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
dest = eval(self.data_path)
|
||||
if self.filepath_property:
|
||||
setattr(dest, self.filepath_property, self.filepath)
|
||||
if self.filename_property:
|
||||
setattr(dest, self.filename_property, self.filename)
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class PlPyAttributeNodeOperator(NodeOperator, bpy.types.Operator):
|
||||
bl_idname = "node.plasma_attributes_to_node"
|
||||
bl_label = "R"
|
||||
bl_options = {"INTERNAL"}
|
||||
|
||||
python_path = StringProperty(subtype="FILE_PATH")
|
||||
node_path = StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
from ..plasma_attributes import get_attributes
|
||||
attribs = get_attributes(self.python_path)
|
||||
|
||||
node = eval(self.node_path)
|
||||
node_attrib_map = node.attribute_map
|
||||
node_attribs = node.attributes
|
||||
|
||||
# Remove any that p00fed
|
||||
for cached in node.attributes:
|
||||
if cached.attribute_id not in attribs:
|
||||
node_attribs.remove(cached)
|
||||
|
||||
# Update or create
|
||||
for idx, attrib in attribs.items():
|
||||
cached = node_attrib_map.get(idx, None)
|
||||
if cached is None:
|
||||
cached = node_attribs.add()
|
||||
cached.attribute_id = idx
|
||||
cached.attribute_type = attrib["type"]
|
||||
cached.attribute_name = attrib["name"]
|
||||
cached.attribute_description = attrib["desc"]
|
||||
default = attrib.get("default", None)
|
||||
if default is not None and cached.is_simple_value:
|
||||
cached.simple_value = default
|
||||
|
||||
# Manually cause the node to update its inputs
|
||||
node.update()
|
||||
return {"FINISHED"}
|
Reference in New Issue
Block a user