702 lines
24 KiB
Python
Executable file
702 lines
24 KiB
Python
Executable file
import shutil
|
|
import bpy
|
|
import os
|
|
import platform
|
|
import json
|
|
from bpy.props import *
|
|
import subprocess
|
|
import atexit
|
|
import webbrowser
|
|
|
|
def defaultSettings():
|
|
wrd = bpy.data.worlds[0]
|
|
wrd['TargetVersion'] = "15.11.0"
|
|
wrd['TargetEnum'] = 3
|
|
wrd['TargetRenderer'] = 0
|
|
wrd['TargetProjectName'] = "myproject"
|
|
wrd['TargetProjectPackage'] = "myproject"
|
|
wrd['TargetProjectWidth'] = 1136
|
|
wrd['TargetProjectHeight'] = 640
|
|
wrd['TargetScene'] = bpy.data.scenes[0].name
|
|
wrd['TargetGravity'] = bpy.data.scenes[0].name
|
|
wrd['TargetClear'] = bpy.data.worlds[0].name
|
|
wrd['TargetAA'] = 1
|
|
wrd['TargetPhysics'] = 1
|
|
wrd['TargetAutoBuildNodes'] = (True)
|
|
wrd['TargetMinimize'] = (True)
|
|
# Make sure we are using cycles
|
|
if bpy.data.scenes[0].render.engine == 'BLENDER_RENDER':
|
|
for scene in bpy.data.scenes:
|
|
scene.render.engine = 'CYCLES'
|
|
|
|
# Store properties in the world object
|
|
def initWorldProperties():
|
|
bpy.types.World.TargetVersion = StringProperty(name = "CGVersion")
|
|
bpy.types.World.TargetEnum = EnumProperty(
|
|
items = [('OSX', 'OSX', 'OSX'),
|
|
('Windows', 'Windows', 'Windows'),
|
|
('Linux', 'Linux', 'Linux'),
|
|
('HTML5', 'HTML5', 'HTML5'),
|
|
('iOS', 'iOS', 'iOS'),
|
|
('Android', 'Android', 'Android')],
|
|
name = "Target")
|
|
bpy.types.World.TargetProjectName = StringProperty(name = "Name")
|
|
bpy.types.World.TargetProjectPackage = StringProperty(name = "Package")
|
|
bpy.types.World.TargetProjectWidth = IntProperty(name = "Width")
|
|
bpy.types.World.TargetProjectHeight = IntProperty(name = "Height")
|
|
bpy.types.World.TargetScene = StringProperty(name = "Scene")
|
|
bpy.types.World.TargetGravity = StringProperty(name = "Gravity")
|
|
bpy.types.World.TargetClear = StringProperty(name = "Clear")
|
|
bpy.types.World.TargetAA = EnumProperty(
|
|
items = [('Disabled', 'Disabled', 'Disabled'),
|
|
('2X', '2X', '2X')],
|
|
name = "Anti-aliasing")
|
|
bpy.types.World.TargetPhysics = EnumProperty(
|
|
items = [('Disabled', 'Disabled', 'Disabled'),
|
|
('Bullet', 'Bullet', 'Bullet')],
|
|
name = "Physics")
|
|
bpy.types.World.TargetAutoBuildNodes = BoolProperty(name = "Auto-build nodes")
|
|
bpy.types.World.TargetMinimize = BoolProperty(name = "Minimize")
|
|
|
|
# Default settings
|
|
# todo: check version
|
|
if not 'TargetVersion' in bpy.data.worlds[0]:
|
|
defaultSettings()
|
|
|
|
# Make sure we are using nodes for every material
|
|
# Use material nodes
|
|
for mat in bpy.data.materials:
|
|
bpy.ops.cycles.use_shading_nodes({"material":mat})
|
|
# Use world nodes
|
|
for wrd in bpy.data.worlds:
|
|
bpy.ops.cycles.use_shading_nodes({"world":wrd})
|
|
|
|
return
|
|
|
|
# Store properties in world
|
|
initWorldProperties()
|
|
|
|
# Info panel play
|
|
def draw_play_item(self, context):
|
|
layout = self.layout
|
|
layout.operator("cg.play")
|
|
|
|
# Menu in tools region
|
|
class ToolsPanel(bpy.types.Panel):
|
|
bl_label = "Cycles Game"
|
|
bl_space_type = "PROPERTIES"
|
|
bl_region_type = "WINDOW"
|
|
bl_context = "render"
|
|
|
|
# Info panel play
|
|
bpy.types.INFO_HT_header.prepend(draw_play_item)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
wrd = bpy.data.worlds[0]
|
|
layout.prop(wrd, 'TargetProjectName')
|
|
layout.prop(wrd, 'TargetProjectPackage')
|
|
layout.prop(wrd, 'TargetProjectWidth')
|
|
layout.prop(wrd, 'TargetProjectHeight')
|
|
layout.prop_search(wrd, "TargetScene", bpy.data, "scenes", "Scene")
|
|
layout.prop(wrd, 'TargetEnum')
|
|
layout.operator("cg.build")
|
|
row = layout.row(align=True)
|
|
row.alignment = 'EXPAND'
|
|
row.operator("cg.folder")
|
|
row.operator("cg.clean")
|
|
layout.prop_search(wrd, "TargetGravity", bpy.data, "scenes", "Gravity")
|
|
layout.prop_search(wrd, "TargetClear", bpy.data, "worlds", "Clear Color")
|
|
layout.prop(wrd, 'TargetAA')
|
|
layout.prop(wrd, 'TargetPhysics')
|
|
row = layout.row()
|
|
row.prop(wrd, 'TargetAutoBuildNodes')
|
|
if wrd['TargetAutoBuildNodes'] == False:
|
|
row.operator("cg.buildnodes")
|
|
layout.prop(wrd, 'TargetMinimize')
|
|
layout.operator("cg.defaultsettings")
|
|
|
|
# Used to output json
|
|
class Object:
|
|
def to_JSON(self):
|
|
if bpy.data.worlds[0]['TargetMinimize'] == True:
|
|
return json.dumps(self, default=lambda o: o.__dict__, separators=(',',':'))
|
|
else:
|
|
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
|
|
|
|
# Creates asset node in project.kha
|
|
def createAsset(filename, type, splitExt=None):
|
|
if (splitExt == None):
|
|
splitExt = True
|
|
|
|
str = filename.split(".")
|
|
l = len(str)
|
|
name = str[0]
|
|
for i in range(1, l - 1):
|
|
name += "." + str[i]
|
|
|
|
if (splitExt == False):
|
|
name = filename
|
|
|
|
x = Object()
|
|
x.type = type
|
|
x.file = filename
|
|
x.name = name
|
|
return x
|
|
|
|
# Creates room node in project.kha
|
|
def createRoom(name, assets):
|
|
x = Object()
|
|
x.name = name
|
|
x.parent = None
|
|
x.neighbours = []
|
|
x.assets = []
|
|
for a in assets:
|
|
if a.type == 'font':
|
|
x.assets.append(a.name + str(a.size) + '.kravur')
|
|
else:
|
|
x.assets.append(a.name)
|
|
return x
|
|
|
|
# Convert Blender data into game data
|
|
def exportGameData():
|
|
# TODO: Set armatures to center of world so skin transform is zero
|
|
armatures = []
|
|
for o in bpy.data.objects:
|
|
if o.type == 'ARMATURE':
|
|
a = Object()
|
|
a.armature = o
|
|
a.x = o.location.x
|
|
a.y = o.location.y
|
|
a.z = o.location.z
|
|
armatures.append(a)
|
|
o.location.x = 0
|
|
o.location.y = 0
|
|
o.location.z = 0
|
|
|
|
# Export scene data
|
|
for scene in bpy.data.scenes:
|
|
if scene.name[0] != '.': # Skip hidden scenes
|
|
bpy.ops.export_scene.lue({"scene":scene}, filepath='Assets/' + scene.name + '.json')
|
|
|
|
# Move armatures back
|
|
for a in armatures:
|
|
a.armature.location.x = a.x
|
|
a.armature.location.y = a.y
|
|
a.armature.location.z = a.z
|
|
|
|
# Export project file
|
|
# x = Object()
|
|
# x.format = 3
|
|
# x.game = Object()
|
|
# x.game.name = bpy.data.worlds[0]['TargetProjectName']
|
|
# x.game.width = bpy.data.worlds[0]['TargetProjectWidth']
|
|
# x.game.height = bpy.data.worlds[0]['TargetProjectHeight']
|
|
# if bpy.data.worlds[0]['TargetAA'] == 1:
|
|
# x.game.antiAliasingSamples = 2
|
|
# x.libraries = ["cyclesgame", "haxebullet"]
|
|
# # Defined libraries
|
|
# for o in bpy.data.worlds[0].my_liblist:
|
|
# if o.enabled_prop:
|
|
# x.libraries.append(o.name)
|
|
# # Assets
|
|
# x.assets = []
|
|
# x.rooms = []
|
|
# # - Data
|
|
# x.assets.append(createAsset("data.json", "blob"))
|
|
# # - Scenes
|
|
# for s in bpy.data.scenes:
|
|
# x.assets.append(createAsset(s.name + ".json", "blob"))
|
|
# # - Defined assets
|
|
# for o in bpy.data.worlds[0].my_list:
|
|
# if o.enabled_prop:
|
|
# if (o.type_prop == 'Atlas'):
|
|
# x.assets.append(createAsset(o.name + "_metadata.json", "blob"))
|
|
# x.assets.append(createAsset(o.name + "_atlas.png", "image"))
|
|
# elif (o.type_prop == 'Font'):
|
|
# asset = createAsset(o.name, "font")
|
|
# asset.size = o.size_prop
|
|
# x.assets.append(asset)
|
|
# else:
|
|
# typeName = o.type_prop.lower()
|
|
# x.assets.append(createAsset(o.name, typeName))
|
|
# # - Rooms
|
|
# x.rooms.append(createRoom("room1", x.assets))
|
|
# # Write project file
|
|
# with open('project.kha', 'w') as f:
|
|
# f.write(x.to_JSON())
|
|
|
|
|
|
# # Export scene properties
|
|
# data = Object()
|
|
|
|
# # Objects
|
|
# objs = []
|
|
# for o in bpy.data.objects:
|
|
# x = Object()
|
|
# x.name = o.name
|
|
# x.traits = []
|
|
# for t in o.my_traitlist:
|
|
# # Disabled trait
|
|
# if t.enabled_prop == False:
|
|
# continue
|
|
|
|
# y = Object()
|
|
# y.type = t.type_prop
|
|
|
|
# # Script
|
|
# if y.type == 'Script':
|
|
# y.class_name = t.class_name_prop
|
|
# # Custom traits
|
|
# elif y.type == 'Mesh Renderer':
|
|
# y.class_name = 'MeshRenderer'
|
|
# if t.default_material_prop: # Use object material
|
|
# y.material = ""
|
|
# else:
|
|
# y.material = t.material_prop
|
|
# y.lighting = t.lighting_prop
|
|
# y.cast_shadow = t.cast_shadow_prop
|
|
# y.receive_shadow = t.receive_shadow_prop
|
|
# elif y.type == 'Custom Renderer':
|
|
# y.class_name = t.class_name_prop
|
|
|
|
# if t.default_material_prop: # Use object material
|
|
# y.material = ""
|
|
# else:
|
|
# y.material = t.material_prop
|
|
|
|
# y.shader = t.shader_prop
|
|
# y.data = t.data_prop
|
|
# elif y.type == 'Billboard Renderer':
|
|
# y.type = 'Custom Renderer'
|
|
# y.class_name = 'BillboardRenderer'
|
|
# if t.default_material_prop: # Use object material
|
|
# y.material = ""
|
|
# else:
|
|
# y.material = t.material_prop
|
|
# y.shader = 'billboardshader'
|
|
# elif y.type == 'Particles Renderer':
|
|
# y.type = 'Custom Renderer'
|
|
# y.class_name = 'ParticlesRenderer'
|
|
# if t.default_material_prop: # Use object material
|
|
# y.material = ""
|
|
# else:
|
|
# y.material = t.material_prop
|
|
# y.shader = 'particlesshader'
|
|
# y.data = t.data_prop
|
|
# # Convert to scripts
|
|
# elif y.type == 'Nodes':
|
|
# y.type = 'Script'
|
|
# y.class_name = t.nodes_name_prop.replace('.', '_')
|
|
# elif y.type == 'Scene Instance':
|
|
# y.type = 'Script'
|
|
# y.class_name = "SceneInstance:'" + t.scene_prop + "'"
|
|
# elif y.type == 'Animation':
|
|
# y.type = 'Script'
|
|
# y.class_name = "Animation:'" + t.start_track_name_prop + "':"
|
|
# # Names
|
|
# anim_names = []
|
|
# anim_starts = []
|
|
# anim_ends = []
|
|
# for animt in o.my_animationtraitlist:
|
|
# if animt.enabled_prop == False:
|
|
# continue
|
|
# anim_names.append(animt.name)
|
|
# anim_starts.append(animt.start_prop)
|
|
# anim_ends.append(animt.end_prop)
|
|
# y.class_name += str(anim_names) + ":"
|
|
# y.class_name += str(anim_starts) + ":"
|
|
# y.class_name += str(anim_ends)
|
|
# # Armature offset
|
|
# for a in armatures:
|
|
# if o.parent == a.armature:
|
|
# y.class_name += ":" + str(a.x) + ":" + str(a.y) + ":" + str(a.z)
|
|
# break
|
|
|
|
# elif y.type == 'Camera':
|
|
# y.type = 'Script'
|
|
# cam = bpy.data.cameras[t.camera_link_prop]
|
|
# if cam.type == 'PERSP':
|
|
# y.class_name = 'PerspectiveCamera'
|
|
# elif cam.type == 'ORTHO':
|
|
# y.class_name = 'OrthoCamera'
|
|
# elif y.type == 'Light':
|
|
# y.type = 'Script'
|
|
# y.class_name = 'Light'
|
|
# elif y.type == 'Rigid Body':
|
|
# if bpy.data.worlds[0]['TargetPhysics'] == 0:
|
|
# continue
|
|
# y.type = 'Script'
|
|
# # Get rigid body
|
|
# if t.default_body_prop == True:
|
|
# rb = o.rigid_body
|
|
# else:
|
|
# rb = bpy.data.objects[t.body_link_prop].rigid_body
|
|
# shape = '0' # BOX
|
|
# if t.custom_shape_prop == True:
|
|
# if t.custom_shape_type_prop == 'Terrain':
|
|
# shape = '7'
|
|
# elif t.custom_shape_type_prop == 'Static Mesh':
|
|
# shape = '8'
|
|
# elif rb.collision_shape == 'SPHERE':
|
|
# shape = '1'
|
|
# elif rb.collision_shape == 'CONVEX_HULL':
|
|
# shape = '2'
|
|
# elif rb.collision_shape == 'MESH':
|
|
# shape = '3'
|
|
# elif rb.collision_shape == 'CONE':
|
|
# shape = '4'
|
|
# elif rb.collision_shape == 'CYLINDER':
|
|
# shape = '5'
|
|
# elif rb.collision_shape == 'CAPSULE':
|
|
# shape = '6'
|
|
# body_mass = 0
|
|
# if rb.enabled:
|
|
# body_mass = rb.mass
|
|
# y.class_name = 'RigidBody:' + str(body_mass) + \
|
|
# ':' + shape + \
|
|
# ":" + str(rb.friction) + \
|
|
# ":" + str(t.shape_size_scale_prop[0]) + \
|
|
# ":" + str(t.shape_size_scale_prop[1]) + \
|
|
# ":" + str(t.shape_size_scale_prop[2])
|
|
|
|
# # Append trait
|
|
# x.traits.append(y)
|
|
|
|
# # Material slots
|
|
# x.materials = []
|
|
# if o.material_slots:
|
|
# for ms in o.material_slots:
|
|
# x.materials.append(ms.name)
|
|
# objs.append(x)
|
|
|
|
# # Materials
|
|
# mats = []
|
|
# for m in bpy.data.materials:
|
|
# # Make sure material is using nodes
|
|
# if m.node_tree == None:
|
|
# continue
|
|
# x = Object()
|
|
# x.name = m.name
|
|
# nodes = m.node_tree.nodes
|
|
# # Diffuse
|
|
# if 'Diffuse BSDF' in nodes:
|
|
# x.diffuse = True
|
|
# dnode = nodes['Diffuse BSDF']
|
|
# dcol = dnode.inputs[0].default_value
|
|
# x.diffuse_color = [dcol[0], dcol[1], dcol[2], dcol[3]]
|
|
# else:
|
|
# x.diffuse = False
|
|
# # Glossy
|
|
# if 'Glossy BSDF' in nodes:
|
|
# x.glossy = True
|
|
# gnode = nodes['Glossy BSDF']
|
|
# gcol = gnode.inputs[0].default_value
|
|
# x.glossy_color = [gcol[0], gcol[1], gcol[2], gcol[3]]
|
|
# x.roughness = gnode.inputs[1].default_value
|
|
# else:
|
|
# x.glossy = False
|
|
# # Texture
|
|
# if 'Image Texture' in nodes:
|
|
# x.texture = nodes['Image Texture'].image.name.split(".")[0]
|
|
# else:
|
|
# x.texture = ''
|
|
# mats.append(x)
|
|
|
|
# # Output data json
|
|
# data.objects = objs
|
|
# data.materials = mats
|
|
# data.scene = bpy.data.worlds[0]['TargetScene']
|
|
# data.packageName = bpy.data.worlds[0]['TargetProjectPackage']
|
|
# gravityscn = bpy.data.scenes[bpy.data.worlds[0]['TargetGravity']]
|
|
# if gravityscn.use_gravity:
|
|
# data.gravity = [gravityscn.gravity[0], gravityscn.gravity[1], gravityscn.gravity[2]]
|
|
# else:
|
|
# data.gravity = [0.0, 0.0, 0.0]
|
|
# clearwrd = bpy.data.worlds[bpy.data.worlds[0]['TargetClear']]
|
|
# # Only 'Background' surface for now
|
|
# clearcol = clearwrd.node_tree.nodes['Background'].inputs[0].default_value
|
|
# data.clear = [clearcol[0], clearcol[1], clearcol[2], clearcol[3]]
|
|
# data.physics = bpy.data.worlds[0]['TargetPhysics']
|
|
# with open('Assets/data.json', 'w') as f:
|
|
# f.write(data.to_JSON())
|
|
|
|
# Write Main.hx
|
|
#if not os.path.isfile('Sources/Main.hx'):
|
|
with open('Sources/Main.hx', 'w') as f:
|
|
f.write(
|
|
"""// Auto-generated
|
|
package ;
|
|
class Main {
|
|
public static function main() {
|
|
lue.sys.CompileTime.importPackage('lue.trait');
|
|
lue.sys.CompileTime.importPackage('cycles.trait');
|
|
lue.sys.CompileTime.importPackage('""" + bpy.data.worlds[0]['TargetProjectPackage'] + """');
|
|
#if js
|
|
untyped __js__("
|
|
function loadScript(url, callback) {
|
|
var head = document.getElementsByTagName('head')[0];
|
|
var script = document.createElement('script');
|
|
script.type = 'text/javascript';
|
|
script.src = url;
|
|
script.onreadystatechange = callback;
|
|
script.onload = callback;
|
|
head.appendChild(script);
|
|
}
|
|
");
|
|
untyped loadScript('ammo.js', start);
|
|
#else
|
|
start();
|
|
#end
|
|
}
|
|
static function start() {
|
|
kha.System.init("CyclesGame", 1136, 640, function() {
|
|
new lue.App(cycles.Root);
|
|
});
|
|
}
|
|
}
|
|
""")
|
|
|
|
def buildProject(self, build_type=0):
|
|
# Save scripts
|
|
#area = bpy.context.area
|
|
#old_type = area.type
|
|
#area.type = 'TEXT_EDITOR'
|
|
#for text in bpy.data.texts:
|
|
#area.spaces[0].text = text
|
|
#bpy.ops.text.save()
|
|
##bpy.ops.text.save()
|
|
#area.type = old_type
|
|
|
|
# Auto-build nodes
|
|
if (bpy.data.worlds[0]['TargetAutoBuildNodes'] == True):
|
|
buildNodeTrees()
|
|
|
|
# Set dir
|
|
s = bpy.data.filepath.split(os.path.sep)
|
|
name = s.pop()
|
|
name = name.split(".")
|
|
name = name[0]
|
|
fp = os.path.sep.join(s)
|
|
os.chdir(fp)
|
|
|
|
# Save blend
|
|
bpy.ops.wm.save_mainfile()
|
|
|
|
# Export
|
|
exportGameData()
|
|
|
|
# Set build command
|
|
if (bpy.data.worlds[0]['TargetEnum'] == 0):
|
|
bashCommand = "-t osx"
|
|
elif (bpy.data.worlds[0]['TargetEnum'] == 1):
|
|
bashCommand = "-t windows"
|
|
elif (bpy.data.worlds[0]['TargetEnum'] == 2):
|
|
bashCommand = "-t linux"
|
|
elif (bpy.data.worlds[0]['TargetEnum'] == 3):
|
|
bashCommand = "-t html5"
|
|
elif (bpy.data.worlds[0]['TargetEnum'] == 4):
|
|
bashCommand = "-t ios"
|
|
elif (bpy.data.worlds[0]['TargetEnum'] == 5):
|
|
bashCommand = "-t android_native"
|
|
|
|
# Build
|
|
haxelib_path = "haxelib"
|
|
if platform.system() == 'Darwin':
|
|
haxelib_path = "/usr/local/bin/haxelib"
|
|
|
|
prefix = haxelib_path + " run kha "
|
|
|
|
output = subprocess.check_output([haxelib_path + " path cyclesgame"], shell=True)
|
|
output = str(output).split("\\n")[0].split("'")[1]
|
|
scripts_path = output + "blender/"
|
|
|
|
blender_path = bpy.app.binary_path
|
|
blend_path = bpy.data.filepath
|
|
p = subprocess.Popen([blender_path, blend_path, '-b', '-P', scripts_path + 'build.py', '--', prefix + bashCommand, str(build_type), str(bpy.data.worlds[0]['TargetEnum'])])
|
|
atexit.register(p.terminate)
|
|
|
|
self.report({'INFO'}, "Building, see console...")
|
|
|
|
def cleanProject(self):
|
|
# Set dir
|
|
s = bpy.data.filepath.split(os.path.sep)
|
|
name = s.pop()
|
|
name = name.split(".")
|
|
name = name[0]
|
|
fp = os.path.sep.join(s)
|
|
os.chdir(fp)
|
|
|
|
# Remove build dir
|
|
if os.path.isdir("build"):
|
|
shutil.rmtree('build')
|
|
|
|
# Remove compiled nodes
|
|
if (bpy.data.worlds[0]['TargetAutoBuildNodes'] == True):
|
|
path = 'Sources/' + bpy.data.worlds[0].TargetProjectPackage.replace(".", "/") + "/"
|
|
for node_group in bpy.data.node_groups:
|
|
node_group_name = node_group.name.replace('.', '_')
|
|
os.remove(path + node_group_name + '.hx')
|
|
|
|
self.report({'INFO'}, "Done")
|
|
|
|
# Play
|
|
class OBJECT_OT_PLAYButton(bpy.types.Operator):
|
|
bl_idname = "cg.play"
|
|
bl_label = "Play"
|
|
|
|
def execute(self, context):
|
|
buildProject(self, 1)
|
|
return{'FINISHED'}
|
|
|
|
# Build
|
|
class OBJECT_OT_BUILDButton(bpy.types.Operator):
|
|
bl_idname = "cg.build"
|
|
bl_label = "Build"
|
|
|
|
def execute(self, context):
|
|
buildProject(self, 0)
|
|
return{'FINISHED'}
|
|
|
|
# Open project folder
|
|
class OBJECT_OT_FOLDERButton(bpy.types.Operator):
|
|
bl_idname = "cg.folder"
|
|
bl_label = "Folder"
|
|
|
|
def execute(self, context):
|
|
s = bpy.data.filepath.split(os.path.sep)
|
|
name = s.pop()
|
|
name = name.split(".")
|
|
name = name[0]
|
|
fp = os.path.sep.join(s)
|
|
|
|
webbrowser.open('file://' + fp)
|
|
return{'FINISHED'}
|
|
|
|
# Clean project
|
|
class OBJECT_OT_CLEANButton(bpy.types.Operator):
|
|
bl_idname = "cg.clean"
|
|
bl_label = "Clean"
|
|
|
|
def execute(self, context):
|
|
cleanProject(self)
|
|
return{'FINISHED'}
|
|
|
|
# Build nodes
|
|
class OBJECT_OT_BUILDNODESButton(bpy.types.Operator):
|
|
bl_idname = "cg.buildnodes"
|
|
bl_label = "Build Nodes"
|
|
|
|
def execute(self, context):
|
|
|
|
buildNodeTrees();
|
|
|
|
self.report({'INFO'}, "Nodes built")
|
|
return{'FINISHED'}
|
|
|
|
# Default settings
|
|
class OBJECT_OT_DEFAULTSETTINGSButton(bpy.types.Operator):
|
|
bl_idname = "cg.defaultsettings"
|
|
bl_label = "Default Settings"
|
|
|
|
def execute(self, context):
|
|
defaultSettings()
|
|
self.report({'INFO'}, "Defaults set")
|
|
return{'FINISHED'}
|
|
|
|
|
|
|
|
|
|
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
|
|
if not os.path.exists('Sources/' + bpy.data.worlds[0].TargetProjectPackage.replace(".", "/")):
|
|
os.makedirs('Sources/' + bpy.data.worlds[0].TargetProjectPackage.replace(".", "/"))
|
|
|
|
# Export node scripts
|
|
for node_group in bpy.data.node_groups:
|
|
buildNodeTree(node_group)
|
|
|
|
def buildNodeTree(node_group):
|
|
rn = getRootNode(node_group)
|
|
|
|
path = 'Sources/' + bpy.data.worlds[0].TargetProjectPackage.replace(".", "/") + "/"
|
|
|
|
node_group_name = node_group.name.replace('.', '_')
|
|
|
|
with open(path + node_group_name + '.hx', 'w') as f:
|
|
f.write('package ' + bpy.data.worlds[0].TargetProjectPackage + ';\n\n')
|
|
f.write('import cycles.node.*;\n\n')
|
|
f.write('class ' + node_group_name + ' extends cycles.trait.NodeExecutor {\n\n')
|
|
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
|
|
|
|
# Registration
|
|
bpy.utils.register_module(__name__)
|