2015-10-30 13:23:09 +01:00
|
|
|
import bpy
|
|
|
|
from bpy.types import NodeTree, Node, NodeSocket
|
|
|
|
from bpy.props import *
|
2016-01-26 14:36:55 +01:00
|
|
|
import os
|
|
|
|
import sys
|
2015-10-30 13:23:09 +01:00
|
|
|
# Implementation of custom nodes from Python
|
|
|
|
|
|
|
|
# Derived from the NodeTree base type, similar to Menu, Operator, Panel, etc.
|
2016-01-11 21:10:33 +01:00
|
|
|
class CGTree(NodeTree):
|
2015-10-30 13:23:09 +01:00
|
|
|
# Description string
|
2015-11-10 22:07:28 +01:00
|
|
|
'''Logic nodes'''
|
2015-10-30 13:23:09 +01:00
|
|
|
# Optional identifier string. If not explicitly defined, the python class name is used.
|
2016-01-11 21:10:33 +01:00
|
|
|
bl_idname = 'CGTreeType'
|
2015-10-30 13:23:09 +01:00
|
|
|
# Label for nice name display
|
2015-11-10 22:07:28 +01:00
|
|
|
bl_label = 'CG Node Tree'
|
2015-10-30 13:23:09 +01:00
|
|
|
# Icon identifier
|
|
|
|
# NOTE: If no icon is defined, the node tree will not show up in the editor header!
|
2016-01-26 14:36:55 +01:00
|
|
|
# This can be used to make additional tree types for groups and similar nodes (see below)
|
|
|
|
# Only one base tree class is needed in the editor for selecting the general category
|
2015-10-30 13:23:09 +01:00
|
|
|
bl_icon = 'GAME'
|
|
|
|
|
|
|
|
#def update(self):
|
|
|
|
#print("runing tree")
|
|
|
|
|
|
|
|
# Mix-in class for all custom nodes in this tree type.
|
|
|
|
# Defines a poll function to enable instantiation.
|
2016-01-11 21:10:33 +01:00
|
|
|
class CGTreeNode:
|
2015-10-30 13:23:09 +01:00
|
|
|
@classmethod
|
|
|
|
def poll(cls, ntree):
|
2016-01-11 21:10:33 +01:00
|
|
|
return ntree.bl_idname == 'CGTreeType'
|
2015-10-30 13:23:09 +01:00
|
|
|
|
|
|
|
|
|
|
|
# Derived from the Node base type.
|
2016-01-11 21:10:33 +01:00
|
|
|
class TransformNode(Node, CGTreeNode):
|
2015-10-30 13:23:09 +01:00
|
|
|
# Description string
|
|
|
|
'''A custom node'''
|
|
|
|
# Optional identifier string. If not explicitly defined, the python class name is used.
|
|
|
|
bl_idname = 'TransformNodeType'
|
|
|
|
# Label for nice name display
|
|
|
|
bl_label = '@Transform'
|
|
|
|
# Icon identifier
|
|
|
|
bl_icon = 'SOUND'
|
|
|
|
|
|
|
|
# These work just like custom properties in ID data blocks
|
|
|
|
# Extensive information can be found under
|
|
|
|
# http://wiki.blender.org/index.php/Doc:2.6/Manual/Extensions/Python/Properties
|
|
|
|
#objname = bpy.props.StringProperty()
|
|
|
|
|
|
|
|
# Initialization function, called when a new node is created.
|
|
|
|
# This is the most common place to create the sockets for a node, as shown below.
|
|
|
|
# NOTE: this is not the same as the standard __init__ function in Python, which is
|
2016-01-26 14:36:55 +01:00
|
|
|
# a purely internal Python method and unknown to the node system!
|
2015-10-30 13:23:09 +01:00
|
|
|
def init(self, context):
|
|
|
|
self.inputs.new('NodeSocketVector', "Position")
|
|
|
|
self.inputs.new('NodeSocketVector', "Rotation")
|
|
|
|
self.inputs.new('NodeSocketVector', "Scale")
|
|
|
|
|
|
|
|
self.inputs["Scale"].default_value = [1.0, 1.0, 1.0]
|
|
|
|
|
|
|
|
self.outputs.new('NodeSocketString', "Transform")
|
|
|
|
|
|
|
|
# Copy function to initialize a copied node from an existing one.
|
|
|
|
def copy(self, node):
|
|
|
|
print("Copying from node ", node)
|
|
|
|
|
|
|
|
# Free function to clean up on removal.
|
|
|
|
def free(self):
|
|
|
|
print("Removing node ", self, ", Goodbye!")
|
|
|
|
|
|
|
|
# Additional buttons displayed on the node.
|
|
|
|
#def draw_buttons(self, context, layout):
|
|
|
|
#layout.prop_search(self, "objname", context.scene, "objects", text = "")
|
|
|
|
|
|
|
|
# Derived from the Node base type.
|
2016-01-11 21:10:33 +01:00
|
|
|
class TimeNode(Node, CGTreeNode):
|
2015-10-30 13:23:09 +01:00
|
|
|
|
|
|
|
# Description string
|
|
|
|
'''Time node'''
|
|
|
|
# Optional identifier string. If not explicitly defined, the python class name is used.
|
|
|
|
bl_idname = 'TimeNodeType'
|
|
|
|
# Label for nice name display
|
|
|
|
bl_label = 'Time'
|
|
|
|
# Icon identifier
|
|
|
|
bl_icon = 'TIME'
|
|
|
|
|
|
|
|
def init(self, context):
|
|
|
|
self.inputs.new('NodeSocketFloat', "Start")
|
|
|
|
self.inputs.new('NodeSocketFloat', "Stop")
|
|
|
|
self.inputs.new('NodeSocketFloat', "Scale")
|
|
|
|
self.inputs.new('NodeSocketBool', "Enabled")
|
|
|
|
self.inputs.new('NodeSocketBool', "Loop")
|
|
|
|
self.inputs.new('NodeSocketBool', "Reflect")
|
|
|
|
|
|
|
|
self.inputs["Stop"].default_value = -1
|
|
|
|
self.inputs["Scale"].default_value = 1
|
|
|
|
self.inputs["Enabled"].default_value = True
|
|
|
|
|
|
|
|
self.outputs.new('NodeSocketFloat', "Time")
|
|
|
|
|
|
|
|
#def draw_buttons(self, context, layout):
|
|
|
|
#layout.prop(self, "startTime")
|
|
|
|
#layout.prop(self, "stopTime")
|
|
|
|
|
|
|
|
def free(self):
|
|
|
|
print("Removing node ", self, ", Goodbye!")
|
|
|
|
|
2016-01-11 21:10:33 +01:00
|
|
|
class VectorNode(Node, CGTreeNode):
|
2015-10-30 13:23:09 +01:00
|
|
|
bl_idname = 'VectorNodeType'
|
|
|
|
# Label for nice name display
|
|
|
|
bl_label = 'Vector'
|
|
|
|
# Icon identifier
|
|
|
|
bl_icon = 'CURVE_PATH'
|
|
|
|
|
|
|
|
def init(self, context):
|
|
|
|
self.inputs.new('NodeSocketFloat', "X")
|
|
|
|
self.inputs.new('NodeSocketFloat', "Y")
|
|
|
|
self.inputs.new('NodeSocketFloat', "Z")
|
|
|
|
|
|
|
|
self.outputs.new('NodeSocketVector', "Vector")
|
|
|
|
|
|
|
|
def draw_buttons(self, context, layout):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def free(self):
|
|
|
|
print("Removing node ", self, ", Goodbye!")
|
|
|
|
|
|
|
|
def update(self):
|
|
|
|
print("Updating node: ", self.name)
|
|
|
|
render()
|
|
|
|
|
|
|
|
|
2016-01-11 21:10:33 +01:00
|
|
|
class ScaleValueNode(Node, CGTreeNode):
|
2015-10-30 13:23:09 +01:00
|
|
|
bl_idname = 'ScaleValueNodeType'
|
|
|
|
# Label for nice name display
|
|
|
|
bl_label = 'ScaleValue'
|
|
|
|
# Icon identifier
|
|
|
|
bl_icon = 'CURVE_PATH'
|
|
|
|
|
|
|
|
def init(self, context):
|
|
|
|
self.inputs.new('NodeSocketFloat', "Factor")
|
|
|
|
self.inputs.new('NodeSocketFloat', "Value")
|
|
|
|
|
|
|
|
self.inputs["Factor"].default_value = 1.0
|
|
|
|
|
|
|
|
self.outputs.new('NodeSocketFloat', "Value")
|
|
|
|
|
|
|
|
def draw_buttons(self, context, layout):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def free(self):
|
|
|
|
print("Removing node ", self, ", Goodbye!")
|
|
|
|
|
|
|
|
def update(self):
|
|
|
|
print("Updating node: ", self.name)
|
|
|
|
render()
|
|
|
|
|
|
|
|
|
2016-01-11 21:10:33 +01:00
|
|
|
class SineNode(Node, CGTreeNode):
|
2015-10-30 13:23:09 +01:00
|
|
|
bl_idname = 'SineNodeType'
|
|
|
|
# Label for nice name display
|
|
|
|
bl_label = 'Sine'
|
|
|
|
# Icon identifier
|
|
|
|
bl_icon = 'CURVE_PATH'
|
|
|
|
|
|
|
|
def init(self, context):
|
|
|
|
self.inputs.new('NodeSocketFloat', "Value")
|
|
|
|
|
|
|
|
self.outputs.new('NodeSocketFloat', "Value")
|
|
|
|
|
|
|
|
def draw_buttons(self, context, layout):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def free(self):
|
|
|
|
print("Removing node ", self, ", Goodbye!")
|
|
|
|
|
|
|
|
def update(self):
|
|
|
|
print("Updating node: ", self.name)
|
|
|
|
render()
|
|
|
|
|
|
|
|
|
|
|
|
### Node Categories ###
|
|
|
|
# Node categories are a python system for automatically
|
|
|
|
# extending the Add menu, toolbar panels and search operator.
|
|
|
|
# For more examples see release/scripts/startup/nodeitems_builtins.py
|
|
|
|
|
|
|
|
import nodeitems_utils
|
|
|
|
from nodeitems_utils import NodeCategory, NodeItem
|
|
|
|
|
|
|
|
|
|
|
|
# our own base class with an appropriate poll function,
|
|
|
|
# so the categories only show up in our own tree type
|
|
|
|
class MyNodeCategory(NodeCategory):
|
|
|
|
@classmethod
|
|
|
|
def poll(cls, context):
|
2016-01-11 21:10:33 +01:00
|
|
|
return context.space_data.tree_type == 'CGTreeType'
|
2015-10-30 13:23:09 +01:00
|
|
|
|
|
|
|
# all categories in a list
|
|
|
|
node_categories = [
|
|
|
|
# identifier, label, items list
|
|
|
|
MyNodeCategory("TRANSFORMNODES", "Transform Nodes", items=[
|
|
|
|
# our basic node
|
|
|
|
NodeItem("TransformNodeType"),
|
|
|
|
NodeItem("TimeNodeType"),
|
|
|
|
NodeItem("VectorNodeType"),
|
|
|
|
NodeItem("ScaleValueNodeType"),
|
|
|
|
NodeItem("SineNodeType"),
|
|
|
|
]),
|
|
|
|
]
|
|
|
|
|
|
|
|
def register():
|
2015-12-07 20:04:23 +01:00
|
|
|
bpy.utils.register_module(__name__)
|
2015-10-30 13:23:09 +01:00
|
|
|
try:
|
2016-01-11 21:10:33 +01:00
|
|
|
nodeitems_utils.register_node_categories("CG_NODES", node_categories)
|
2015-10-30 13:23:09 +01:00
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
|
|
|
def unregister():
|
2016-01-11 21:10:33 +01:00
|
|
|
nodeitems_utils.unregister_node_categories("CG_NODES")
|
2015-12-07 20:04:23 +01:00
|
|
|
bpy.utils.unregister_module(__name__)
|
2016-01-26 14:36:55 +01:00
|
|
|
|
|
|
|
# Generating node sources
|
|
|
|
def buildNodeTrees():
|
|
|
|
s = bpy.data.filepath.split(os.path.sep)
|
|
|
|
s.pop()
|
|
|
|
fp = os.path.sep.join(s)
|
|
|
|
os.chdir(fp)
|
|
|
|
|
|
|
|
# Make sure package dir exists
|
2016-02-07 23:03:52 +01:00
|
|
|
nodes_path = 'Sources/' + bpy.data.worlds[0].CGProjectPackage.replace(".", "/") + "/node"
|
|
|
|
if not os.path.exists(nodes_path):
|
|
|
|
os.makedirs(nodes_path)
|
2016-01-26 14:36:55 +01:00
|
|
|
|
|
|
|
# Export node scripts
|
|
|
|
for node_group in bpy.data.node_groups:
|
2016-07-10 00:51:39 +02:00
|
|
|
if node_group.bl_idname == 'CGTreeType': # Build only game trees
|
2016-02-08 12:03:20 +01:00
|
|
|
node_group.use_fake_user = True # Keep fake references for now
|
2016-01-26 14:36:55 +01:00
|
|
|
buildNodeTree(node_group)
|
|
|
|
|
|
|
|
def buildNodeTree(node_group):
|
|
|
|
rn = getRootNode(node_group)
|
|
|
|
|
2016-02-07 23:03:52 +01:00
|
|
|
path = 'Sources/' + bpy.data.worlds[0].CGProjectPackage.replace(".", "/") + "/node/"
|
2016-01-26 14:36:55 +01:00
|
|
|
|
|
|
|
node_group_name = node_group.name.replace('.', '_')
|
|
|
|
|
|
|
|
with open(path + node_group_name + '.hx', 'w') as f:
|
2016-02-07 23:03:52 +01:00
|
|
|
f.write('package ' + bpy.data.worlds[0].CGProjectPackage + '.node;\n\n')
|
2016-07-10 00:51:39 +02:00
|
|
|
f.write('import armory.node.*;\n\n')
|
|
|
|
f.write('class ' + node_group_name + ' extends armory.trait.internal.NodeExecutor {\n\n')
|
2016-01-26 14:36:55 +01:00
|
|
|
f.write('\tpublic function new() { super(); requestAdd(add); }\n\n')
|
|
|
|
f.write('\tfunction add() {\n')
|
|
|
|
# Make sure root node exists
|
|
|
|
if rn != None:
|
|
|
|
name = '_' + rn.name.replace(".", "_").replace("@", "")
|
|
|
|
buildNode(node_group, rn, f, [])
|
|
|
|
f.write('\n\t\tstart(' + name + ');\n')
|
|
|
|
f.write('\t}\n')
|
|
|
|
f.write('}\n')
|
|
|
|
|
|
|
|
def buildNode(node_group, node, f, created_nodes):
|
|
|
|
# Get node name
|
|
|
|
name = '_' + node.name.replace(".", "_").replace("@", "")
|
|
|
|
|
|
|
|
# Check if node already exists
|
|
|
|
for n in created_nodes:
|
|
|
|
if n == name:
|
|
|
|
return name
|
|
|
|
|
|
|
|
# Create node
|
|
|
|
type = node.name.split(".")[0].replace("@", "") + "Node"
|
|
|
|
f.write('\t\tvar ' + name + ' = new ' + type + '();\n')
|
|
|
|
created_nodes.append(name)
|
|
|
|
|
|
|
|
# Variables
|
|
|
|
if type == "TransformNode":
|
|
|
|
f.write('\t\t' + name + '.transform = node.transform;\n')
|
|
|
|
|
|
|
|
# Create inputs
|
|
|
|
for inp in node.inputs:
|
|
|
|
# Is linked - find node
|
|
|
|
inpname = ''
|
|
|
|
if inp.is_linked:
|
|
|
|
n = findNodeByLink(node_group, node, inp)
|
|
|
|
inpname = buildNode(node_group, n, f, created_nodes)
|
|
|
|
# Not linked - create node with default values
|
|
|
|
else:
|
|
|
|
inpname = buildDefaultNode(inp)
|
|
|
|
|
|
|
|
# Add input
|
|
|
|
f.write('\t\t' + name + '.inputs.push(' + inpname + ');\n')
|
|
|
|
|
|
|
|
return name
|
|
|
|
|
|
|
|
def findNodeByLink(node_group, to_node, inp):
|
|
|
|
for link in node_group.links:
|
|
|
|
if link.to_node == to_node and link.to_socket == inp:
|
|
|
|
return link.from_node
|
|
|
|
|
|
|
|
def getRootNode(node_group):
|
|
|
|
for n in node_group.nodes:
|
|
|
|
if n.outputs[0].is_linked == False:
|
|
|
|
return n
|
|
|
|
|
|
|
|
def buildDefaultNode(inp):
|
|
|
|
inpname = ''
|
|
|
|
if inp.type == "VECTOR":
|
|
|
|
inpname = 'VectorNode.create(' + str(inp.default_value[0]) + ', ' + str(inp.default_value[1]) + ", " + str(inp.default_value[2]) + ')'
|
|
|
|
elif inp.type == "VALUE":
|
|
|
|
inpname = 'FloatNode.create(' + str(inp.default_value) + ')'
|
|
|
|
elif inp.type == 'BOOLEAN':
|
|
|
|
inpname = 'BoolNode.create(' + str(inp.default_value).lower() + ')'
|
|
|
|
|
|
|
|
return inpname
|