Browse Source

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.
pull/10/head
Adam Johnson 9 years ago
parent
commit
4ebd70d824
  1. 2
      korman/nodes/__init__.py
  2. 1
      korman/nodes/node_conditions.py
  3. 5
      korman/nodes/node_core.py
  4. 314
      korman/nodes/node_python.py
  5. 1
      korman/nodes/node_responder.py
  6. 1
      korman/operators/__init__.py
  7. 87
      korman/operators/op_nodes.py

2
korman/nodes/__init__.py

@ -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...

1
korman/nodes/node_conditions.py

@ -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):

5
korman/nodes/node_core.py

@ -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

@ -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

1
korman/nodes/node_responder.py

@ -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):

1
korman/operators/__init__.py

@ -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

@ -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"}
Loading…
Cancel
Save