|
|
|
@ -20,18 +20,6 @@ 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", |
|
|
|
@ -40,6 +28,61 @@ _single_user_attribs = {
|
|
|
|
|
"ptAttribGrassShader", |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
_attrib2param = { |
|
|
|
|
"ptAttribInt": plPythonParameter.kInt, |
|
|
|
|
"ptAttribFloat": plPythonParameter.kFloat, |
|
|
|
|
"ptAttribBoolean": plPythonParameter.kBoolean, |
|
|
|
|
"ptAttribString": plPythonParameter.kString, |
|
|
|
|
"ptAttribSceneobject": plPythonParameter.kSceneObject, |
|
|
|
|
"ptAttribSceneobjectList": plPythonParameter.kSceneObjectList, |
|
|
|
|
"ptAttribActivator": plPythonParameter.kActivator, |
|
|
|
|
"ptAttribActivatorList": plPythonParameter.kActivator, |
|
|
|
|
"ptAttribNamedActivator": plPythonParameter.kActivator, |
|
|
|
|
"ptAttribResponder": plPythonParameter.kResponder, |
|
|
|
|
"ptAttribResponderList": plPythonParameter.kResponder, |
|
|
|
|
"ptAttribNamedResponder": plPythonParameter.kResponder, |
|
|
|
|
"ptAttribDynamicMap": plPythonParameter.kDynamicText, |
|
|
|
|
"ptAttribGUIDialog": plPythonParameter.kGUIDialog, |
|
|
|
|
"ptAttribExcludeRegion": plPythonParameter.kExcludeRegion, |
|
|
|
|
"ptAttribAnimation": plPythonParameter.kAnimation, |
|
|
|
|
"ptAttribBehavior": plPythonParameter.kBehavior, |
|
|
|
|
"ptAttribMaterial": plPythonParameter.kMaterial, |
|
|
|
|
"ptAttribMaterialList": plPythonParameter.kMaterial, |
|
|
|
|
"ptAttribGUIPopUpMenu": plPythonParameter.kGUIPopUpMenu, |
|
|
|
|
"ptAttribGUISkin": plPythonParameter.kGUISkin, |
|
|
|
|
"ptAttribWaveSet": plPythonParameter.kWaterComponent, |
|
|
|
|
"ptAttribSwimCurrent": plPythonParameter.kSwimCurrentInterface, |
|
|
|
|
"ptAttribClusterList": plPythonParameter.kClusterComponent, |
|
|
|
|
"ptAttribMaterialAnimation": plPythonParameter.kMaterialAnimation, |
|
|
|
|
"ptAttribGrassShader": plPythonParameter.kGrassShaderComponent, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
_attrib_key_types = { |
|
|
|
|
"ptAttribSceneobject": plFactory.ClassIndex("plSceneObject"), |
|
|
|
|
"ptAttribSceneobjectList": plFactory.ClassIndex("plSceneObject"), |
|
|
|
|
"ptAttribActivator": plFactory.ClassIndex("plLogicModifier"), |
|
|
|
|
"ptAttribActivatorList": plFactory.ClassIndex("plLogicModifier"), |
|
|
|
|
"ptAttribNamedActivator": plFactory.ClassIndex("plLogicModifier"), |
|
|
|
|
"ptAttribResponder": plFactory.ClassIndex("plResponderModifier"), |
|
|
|
|
"ptAttribResponderList": plFactory.ClassIndex("plResponderModifier"), |
|
|
|
|
"ptAttribNamedResponder": plFactory.ClassIndex("plResponderModifier"), |
|
|
|
|
"ptAttribDynamicMap": plFactory.ClassIndex("plDynamicTextMap"), |
|
|
|
|
"ptAttribGUIDialog": plFactory.ClassIndex("pfGUIDialogMod"), |
|
|
|
|
"ptAttribExcludeRegion": plFactory.ClassIndex("plExcludeRegionMod"), |
|
|
|
|
"ptAttribAnimation": plFactory.ClassIndex("plAGMasterMod"), |
|
|
|
|
"ptAttribBehavior": plFactory.ClassIndex("plMultistageBehMod"), |
|
|
|
|
"ptAttribMaterial": plFactory.ClassIndex("plLayer"), |
|
|
|
|
"ptAttribMaterialList": plFactory.ClassIndex("plLayer"), |
|
|
|
|
"ptAttribGUIPopUpMenu": plFactory.ClassIndex("pfGUIPopUpMenu"), |
|
|
|
|
"ptAttribGUISkin": plFactory.ClassIndex("pfGUISkin"), |
|
|
|
|
"ptAttribWaveSet": plFactory.ClassIndex("plWaveSet7"), |
|
|
|
|
"ptAttribSwimCurrent": (plFactory.ClassIndex("plSwimCircularCurrentRegion"), |
|
|
|
|
plFactory.ClassIndex("plSwimStraightCurrentRegion")), |
|
|
|
|
"ptAttribClusterList": plFactory.ClassIndex("plClusterGroup"), |
|
|
|
|
"ptAttribMaterialAnimation": plFactory.ClassIndex("plLayerAnimation"), |
|
|
|
|
"ptAttribGrassShader": plFactory.ClassIndex("plGrassShaderMod"), |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
class PlasmaAttribute(bpy.types.PropertyGroup): |
|
|
|
|
attribute_id = IntProperty() |
|
|
|
|
attribute_type = StringProperty() |
|
|
|
@ -76,12 +119,18 @@ class PlasmaPythonFileNode(PlasmaNodeBase, bpy.types.Node):
|
|
|
|
|
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() |
|
|
|
|
class _NoUpdate: |
|
|
|
|
def __init__(self, node): |
|
|
|
|
self._node = node |
|
|
|
|
def __enter__(self): |
|
|
|
|
self._node.no_update = True |
|
|
|
|
def __exit__(self, type, value, traceback): |
|
|
|
|
self._node.no_update = False |
|
|
|
|
|
|
|
|
|
# Now populate that BAMF |
|
|
|
|
def _update_pyfile(self, context): |
|
|
|
|
with self._NoUpdate(self) as _hack: |
|
|
|
|
self.attributes.clear() |
|
|
|
|
self.inputs.clear() |
|
|
|
|
bpy.ops.node.plasma_attributes_to_node(node_path=self.node_path, python_path=self.filepath) |
|
|
|
|
|
|
|
|
|
filename = StringProperty(name="File", |
|
|
|
@ -90,7 +139,7 @@ class PlasmaPythonFileNode(PlasmaNodeBase, bpy.types.Node):
|
|
|
|
|
options={"HIDDEN"}) |
|
|
|
|
|
|
|
|
|
attributes = CollectionProperty(type=PlasmaAttribute, options={"HIDDEN"}) |
|
|
|
|
dirty_attributes = BoolProperty(options={"HIDDEN"}) |
|
|
|
|
no_update = BoolProperty(default=False, options={"HIDDEN", "SKIP_SAVE"}) |
|
|
|
|
|
|
|
|
|
@property |
|
|
|
|
def attribute_map(self): |
|
|
|
@ -111,6 +160,41 @@ class PlasmaPythonFileNode(PlasmaNodeBase, bpy.types.Node):
|
|
|
|
|
operator.filepath_property = "filepath" |
|
|
|
|
operator.filename_property = "filename" |
|
|
|
|
|
|
|
|
|
def get_key(self, exporter, so): |
|
|
|
|
return exporter.mgr.find_create_key(plPythonFileMod, name=self.key_name, so=so) |
|
|
|
|
|
|
|
|
|
def export(self, exporter, bo, so): |
|
|
|
|
pfm = self.get_key(exporter, so).object |
|
|
|
|
pfm.filename = os.path.splitext(self.filename)[0] |
|
|
|
|
attrib_sockets = (i for i in self.inputs if i.is_linked) |
|
|
|
|
for socket in attrib_sockets: |
|
|
|
|
attrib = socket.attribute_type |
|
|
|
|
from_node = socket.links[0].from_node |
|
|
|
|
|
|
|
|
|
param = plPythonParameter() |
|
|
|
|
param.id = socket.attribute_id |
|
|
|
|
param.valueType = _attrib2param[attrib] |
|
|
|
|
if socket.is_simple_value: |
|
|
|
|
param.value = from_node.value |
|
|
|
|
else: |
|
|
|
|
key = from_node.get_key(exporter, so) |
|
|
|
|
if key is None: |
|
|
|
|
msg = "'{}' Node '{}' didn't return a key and therefore will be unavailable to Python".format( |
|
|
|
|
self.id_data.name, from_node.name) |
|
|
|
|
exporter.report.warn(msg, indent=3) |
|
|
|
|
else: |
|
|
|
|
key_type = _attrib_key_types[attrib] |
|
|
|
|
if isinstance(key_type, tuple): |
|
|
|
|
good_key = key.type in key_type |
|
|
|
|
else: |
|
|
|
|
good_key = key.type == key.type |
|
|
|
|
if not good_key: |
|
|
|
|
msg = "'{}' Node '{}' returned an unexpected key type '{}'".format( |
|
|
|
|
self.id_data.name, from_node.name, plFactory.ClassName(key.type)) |
|
|
|
|
exporter.report.warn(msg, indent=3) |
|
|
|
|
param.value = key |
|
|
|
|
pfm.addParameter(param) |
|
|
|
|
|
|
|
|
|
def _get_attrib_sockets(self, idx): |
|
|
|
|
for i in self.inputs: |
|
|
|
|
if i.attribute_id == idx: |
|
|
|
@ -124,32 +208,35 @@ class PlasmaPythonFileNode(PlasmaNodeBase, bpy.types.Node):
|
|
|
|
|
new_pos = i |
|
|
|
|
break |
|
|
|
|
old_pos = len(self.inputs) |
|
|
|
|
socket = self.inputs.new("PlasmaPythonFileNodeSocket", "", "") |
|
|
|
|
socket = self.inputs.new("PlasmaPythonFileNodeSocket", attrib.attribute_name) |
|
|
|
|
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: |
|
|
|
|
if self.no_update: |
|
|
|
|
return |
|
|
|
|
with self._NoUpdate(self) as _no_recurse: |
|
|
|
|
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(idx): |
|
|
|
|
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(idx)) |
|
|
|
|
if not inputs: |
|
|
|
|
self._make_attrib_socket(attrib, empty) |
|
|
|
|
while len(unconnected) > 1: |
|
|
|
|
self.inputs.remove(unconnected.pop()) |
|
|
|
|
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): |
|
|
|
@ -217,6 +304,15 @@ class PlasmaAttribNodeBase(PlasmaNodeBase):
|
|
|
|
|
return socket.links[0].to_socket |
|
|
|
|
return None |
|
|
|
|
|
|
|
|
|
@classmethod |
|
|
|
|
def register(cls): |
|
|
|
|
pl_attrib = cls.pl_attrib |
|
|
|
|
if isinstance(pl_attrib, tuple): |
|
|
|
|
color = _attrib_colors.get(pl_attrib, None) |
|
|
|
|
if color is not None: |
|
|
|
|
for i in pl_attrib: |
|
|
|
|
_attrib_colors[i] = color |
|
|
|
|
|
|
|
|
|
def update(self): |
|
|
|
|
pl_id = self.pl_attrib |
|
|
|
|
socket = self.outputs[0] |
|
|
|
@ -295,6 +391,54 @@ class PlasmaAttribNumericNode(PlasmaAttribNodeBase, bpy.types.Node):
|
|
|
|
|
self.value = attrib.simple_value |
|
|
|
|
self.inited = True |
|
|
|
|
|
|
|
|
|
@property |
|
|
|
|
def value(self): |
|
|
|
|
attrib = self.to_socket |
|
|
|
|
if attrib is None or attrib.attribute_type == "ptAttribInt": |
|
|
|
|
return self.value_int |
|
|
|
|
else: |
|
|
|
|
return self.value_float |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PlasmaAttribObjectNode(PlasmaAttribNodeBase, bpy.types.Node): |
|
|
|
|
bl_category = "PYTHON" |
|
|
|
|
bl_idname = "PlasmaAttribObjectNode" |
|
|
|
|
bl_label = "Object Attribute" |
|
|
|
|
|
|
|
|
|
pl_attrib = ("ptAttribSceneobject", "ptAttribSceneobjectList", "ptAttribAnimation") |
|
|
|
|
|
|
|
|
|
object_name = StringProperty(name="Object", |
|
|
|
|
description="Object containing the required data") |
|
|
|
|
|
|
|
|
|
def init(self, context): |
|
|
|
|
super().init(context) |
|
|
|
|
# keep the code simple |
|
|
|
|
self.outputs[0].link_limit = 1 |
|
|
|
|
|
|
|
|
|
def draw_buttons(self, context, layout): |
|
|
|
|
layout.prop_search(self, "object_name", bpy.data, "objects", text=self.attribute_name) |
|
|
|
|
|
|
|
|
|
def get_key(self, exporter, so): |
|
|
|
|
attrib = self.to_socket |
|
|
|
|
if attrib is None: |
|
|
|
|
self.raise_error("must be connected to a Python File node!") |
|
|
|
|
attrib = attrib.attribute_type |
|
|
|
|
|
|
|
|
|
bo = bpy.objects.data.get(self.object_name, None) |
|
|
|
|
if bo is None: |
|
|
|
|
self.raise_error("invalid object specified: '{}'".format(self.object_name)) |
|
|
|
|
ref_so_key = exporter.mgr.find_create_key(plSceneObject, bl=bo) |
|
|
|
|
ref_so = ref_so_key.object |
|
|
|
|
|
|
|
|
|
# Add your attribute type handling here... |
|
|
|
|
if attrib in {"ptAttribSceneobject", "ptAttribSceneobjectList"}: |
|
|
|
|
return ref_so_key |
|
|
|
|
elif attrib == "ptAttribAnimation": |
|
|
|
|
anim = bo.plasma_modifiers.animation |
|
|
|
|
agmod = exporter.mgr.find_create_key(plAGModifier, so=ref_so, name=anim.display_name) |
|
|
|
|
agmaster = exporter.mgr.find_create_key(plAGMasterModifier, so=ref_so, name=anim.display_name) |
|
|
|
|
return agmaster |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PlasmaAttribStringNode(PlasmaAttribNodeBase, bpy.types.Node): |
|
|
|
|
bl_category = "PYTHON" |
|
|
|
@ -312,3 +456,68 @@ class PlasmaAttribStringNode(PlasmaAttribNodeBase, bpy.types.Node):
|
|
|
|
|
attrib = self.to_socket |
|
|
|
|
if attrib is not None: |
|
|
|
|
self.value = attrib.simple_value |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PlasmaAttribTextureNode(PlasmaAttribNodeBase, bpy.types.Node): |
|
|
|
|
bl_category = "PYTHON" |
|
|
|
|
bl_idname = "PlasmaAttribTextureNode" |
|
|
|
|
bl_label = "Texture Attribute" |
|
|
|
|
bl_width_default = 175 |
|
|
|
|
|
|
|
|
|
pl_attrib = ("ptAttribMaterial", "ptAttribMaterialList", |
|
|
|
|
"ptAttribDynamicMap", "ptAttribMaterialAnimation") |
|
|
|
|
material_name = StringProperty(name="Material") |
|
|
|
|
texture_name = StringProperty(name="Texture") |
|
|
|
|
|
|
|
|
|
def init(self, context): |
|
|
|
|
super().init(context) |
|
|
|
|
# keep the code simple |
|
|
|
|
self.outputs[0].link_limit = 1 |
|
|
|
|
|
|
|
|
|
def draw_buttons(self, context, layout): |
|
|
|
|
layout.prop_search(self, "material_name", bpy.data, "materials") |
|
|
|
|
material = bpy.data.materials.get(self.material_name, None) |
|
|
|
|
if material is not None: |
|
|
|
|
layout.prop_search(self, "texture_name", material, "texture_slots") |
|
|
|
|
|
|
|
|
|
def get_key(self, exporter, so): |
|
|
|
|
material = bpy.data.materials.get(self.material_name, None) |
|
|
|
|
if material is None: |
|
|
|
|
self.raise_error("invalid Material '{}'".format(self.material_name)) |
|
|
|
|
tex_slot = material.texture_slots.get(self.texture_name, None) |
|
|
|
|
if tex_slot is None: |
|
|
|
|
self.raise_error("invalid Texture '{}'".format(self.texture_name)) |
|
|
|
|
attrib = self.attribute_type |
|
|
|
|
|
|
|
|
|
# Helpers |
|
|
|
|
texture = tex_slot.texture |
|
|
|
|
is_animated = ((material.animation_data is not None and material.animation_data.action is not None) |
|
|
|
|
or (texture.animation_data is not None and texture.animation_data.action is not None)) |
|
|
|
|
is_dyntext = texture.type == "IMAGE" and texture.image is None |
|
|
|
|
|
|
|
|
|
# Your attribute stuff here... |
|
|
|
|
if attrib == "ptAttribDynamicMap": |
|
|
|
|
if not is_dyntext: |
|
|
|
|
self.raise_error("Texture '{}' is not a Dynamic Text Map".format(self.texture_name)) |
|
|
|
|
name = "{}_{}_DynText".format(self.material_name, self.texture_name) |
|
|
|
|
return exporter.mgr.find_create_key(plDynamicTextMap, name=name, so=so) |
|
|
|
|
elif is_animated: |
|
|
|
|
name = "{}_{}_LayerAnim".format(self.material_name, self.texture_name) |
|
|
|
|
return exporter.mgr.find_create_key(plLayerAnimation, name=name, so=so) |
|
|
|
|
else: |
|
|
|
|
name = "{}_{}".format(self.material_name, self.texture_name) |
|
|
|
|
return exporter.mgr.find_create_key(plLayer, name=name, so=so) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_attrib_colors = { |
|
|
|
|
"ptAttribActivator": (0.031, 0.110, 0.290, 1.0), |
|
|
|
|
"ptAttribActivatorList": (0.451, 0.0, 0.263, 1.0), |
|
|
|
|
"ptAttribBoolean": (0.71, 0.706, 0.655, 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), |
|
|
|
|
|
|
|
|
|
PlasmaAttribNumericNode.pl_attrib: (0.443, 0.439, 0.392, 1.0), |
|
|
|
|
PlasmaAttribObjectNode.pl_attrib: (0.565, 0.267, 0.0, 1.0), |
|
|
|
|
PlasmaAttribTextureNode.pl_attrib: (0.035, 0.353, 0.0, 1.0), |
|
|
|
|
} |
|
|
|
|