2015-12-07 20:04:23 +01:00
|
|
|
import os
|
|
|
|
import sys
|
2015-10-30 13:23:09 +01:00
|
|
|
import shutil
|
|
|
|
import bpy
|
|
|
|
import platform
|
|
|
|
import json
|
|
|
|
from bpy.props import *
|
|
|
|
import subprocess
|
2016-02-08 21:31:53 +01:00
|
|
|
from subprocess import call
|
2015-10-30 13:23:09 +01:00
|
|
|
import atexit
|
|
|
|
import webbrowser
|
2015-12-07 21:05:27 +01:00
|
|
|
import write_data
|
2016-01-28 23:26:10 +01:00
|
|
|
import nodes_logic
|
2016-01-28 14:47:46 +01:00
|
|
|
import nodes_pipeline
|
2016-02-08 14:58:55 +01:00
|
|
|
import nodes_world
|
2016-03-17 19:29:53 +01:00
|
|
|
import path_tracer
|
2016-01-11 13:07:44 +01:00
|
|
|
from armory import ArmoryExporter
|
2015-10-30 13:23:09 +01:00
|
|
|
|
|
|
|
def defaultSettings():
|
2016-02-08 17:28:05 +01:00
|
|
|
wrd = bpy.data.worlds[0]
|
2016-03-08 14:45:22 +01:00
|
|
|
wrd['CGVersion'] = "16.3.0"
|
2016-02-08 17:28:05 +01:00
|
|
|
wrd['CGProjectTarget'] = 0
|
|
|
|
wrd['CGProjectName'] = "cycles_game"
|
|
|
|
wrd['CGProjectPackage'] = "game"
|
|
|
|
wrd['CGProjectWidth'] = 1136
|
|
|
|
wrd['CGProjectHeight'] = 640
|
|
|
|
wrd['CGProjectScene'] = bpy.data.scenes[0].name
|
2016-02-17 01:26:07 +01:00
|
|
|
wrd['CGProjectSamplesPerPixel'] = 1
|
2016-02-08 17:28:05 +01:00
|
|
|
wrd['CGPhysics'] = 0
|
2016-04-12 22:59:06 +02:00
|
|
|
wrd['CGKhafileConfig'] = ''
|
2016-02-08 17:28:05 +01:00
|
|
|
wrd['CGMinimize'] = (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'
|
2015-10-30 13:23:09 +01:00
|
|
|
|
|
|
|
# Store properties in the world object
|
|
|
|
def initWorldProperties():
|
2016-02-08 17:28:05 +01:00
|
|
|
bpy.types.World.CGVersion = StringProperty(name = "CGVersion")
|
|
|
|
bpy.types.World.CGProjectTarget = EnumProperty(
|
|
|
|
items = [('HTML5', 'HTML5', 'HTML5'),
|
|
|
|
('Windows', 'Windows', 'Windows'),
|
|
|
|
('OSX', 'OSX', 'OSX'),
|
|
|
|
('Linux', 'Linux', 'Linux'),
|
|
|
|
('iOS', 'iOS', 'iOS'),
|
|
|
|
('Android', 'Android', 'Android')],
|
|
|
|
name = "Target")
|
|
|
|
bpy.types.World.CGProjectName = StringProperty(name = "Name")
|
|
|
|
bpy.types.World.CGProjectPackage = StringProperty(name = "Package")
|
|
|
|
bpy.types.World.CGProjectWidth = IntProperty(name = "Width")
|
|
|
|
bpy.types.World.CGProjectHeight = IntProperty(name = "Height")
|
|
|
|
bpy.types.World.CGProjectScene = StringProperty(name = "Scene")
|
2016-02-17 01:26:07 +01:00
|
|
|
bpy.types.World.CGProjectSamplesPerPixel = IntProperty(name = "Samples per pixel")
|
2016-02-08 17:28:05 +01:00
|
|
|
bpy.types.World.CGPhysics = EnumProperty(
|
|
|
|
items = [('Disabled', 'Disabled', 'Disabled'),
|
|
|
|
('Bullet', 'Bullet', 'Bullet')],
|
|
|
|
name = "Physics")
|
2016-04-12 22:59:06 +02:00
|
|
|
bpy.types.World.CGKhafileConfig = StringProperty(name = "Config")
|
2016-02-08 17:28:05 +01:00
|
|
|
bpy.types.World.CGMinimize = BoolProperty(name = "Minimize")
|
2015-10-30 13:23:09 +01:00
|
|
|
|
2016-02-08 17:28:05 +01:00
|
|
|
# Default settings
|
|
|
|
if not 'CGVersion' in bpy.data.worlds[0]:
|
|
|
|
defaultSettings()
|
2015-10-30 13:23:09 +01:00
|
|
|
|
2016-02-08 17:28:05 +01:00
|
|
|
# Use material nodes
|
2016-04-08 12:12:21 +02:00
|
|
|
# TODO: Set a proper override context to prevent error output
|
|
|
|
#for mat in bpy.data.materials:
|
|
|
|
# bpy.ops.cycles.use_shading_nodes({"material":mat})
|
2016-02-08 17:28:05 +01:00
|
|
|
# Use world nodes
|
2016-04-08 12:12:21 +02:00
|
|
|
#for wrd in bpy.data.worlds:
|
|
|
|
# bpy.ops.cycles.use_shading_nodes({"world":wrd})
|
2016-02-08 17:28:05 +01:00
|
|
|
|
|
|
|
return
|
2015-10-30 13:23:09 +01:00
|
|
|
|
|
|
|
# Info panel play
|
|
|
|
def draw_play_item(self, context):
|
2016-02-08 17:28:05 +01:00
|
|
|
layout = self.layout
|
|
|
|
layout.operator("cg.play")
|
2015-10-30 13:23:09 +01:00
|
|
|
|
2016-01-17 22:31:31 +01:00
|
|
|
# Menu in render region
|
2015-10-30 13:23:09 +01:00
|
|
|
class ToolsPanel(bpy.types.Panel):
|
2016-02-08 17:28:05 +01:00
|
|
|
bl_label = "Cycles Game"
|
|
|
|
bl_space_type = "PROPERTIES"
|
|
|
|
bl_region_type = "WINDOW"
|
|
|
|
bl_context = "render"
|
2015-10-30 13:23:09 +01:00
|
|
|
|
2016-02-08 17:28:05 +01:00
|
|
|
# Info panel play
|
|
|
|
#bpy.types.INFO_HT_header.prepend(draw_play_item)
|
|
|
|
bpy.types.VIEW3D_HT_header.append(draw_play_item)
|
2015-10-30 13:23:09 +01:00
|
|
|
|
2016-02-08 17:28:05 +01:00
|
|
|
def draw(self, context):
|
|
|
|
layout = self.layout
|
|
|
|
wrd = bpy.data.worlds[0]
|
|
|
|
layout.prop(wrd, 'CGProjectName')
|
|
|
|
layout.prop(wrd, 'CGProjectPackage')
|
|
|
|
row = layout.row()
|
|
|
|
row.prop(wrd, 'CGProjectWidth')
|
|
|
|
row.prop(wrd, 'CGProjectHeight')
|
|
|
|
layout.prop_search(wrd, "CGProjectScene", bpy.data, "scenes", "Scene")
|
|
|
|
layout.prop(wrd, 'CGProjectTarget')
|
|
|
|
layout.operator("cg.build")
|
|
|
|
row = layout.row(align=True)
|
|
|
|
row.alignment = 'EXPAND'
|
|
|
|
row.operator("cg.folder")
|
|
|
|
row.operator("cg.clean")
|
2016-02-17 01:26:07 +01:00
|
|
|
layout.prop(wrd, 'CGProjectSamplesPerPixel')
|
2016-02-08 17:28:05 +01:00
|
|
|
layout.prop(wrd, 'CGPhysics')
|
2016-04-12 22:59:06 +02:00
|
|
|
layout.prop_search(wrd, "CGKhafileConfig", bpy.data, "texts", "Config")
|
2016-02-08 17:28:05 +01:00
|
|
|
layout.prop(wrd, 'CGMinimize')
|
2015-10-30 13:23:09 +01:00
|
|
|
|
|
|
|
class Object:
|
2016-02-08 17:28:05 +01:00
|
|
|
def to_JSON(self):
|
|
|
|
if bpy.data.worlds[0]['CGMinimize'] == 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)
|
2015-10-30 13:23:09 +01:00
|
|
|
|
2016-04-08 12:12:21 +02:00
|
|
|
def get_export_scene_override(scene):
|
2016-04-08 21:55:07 +02:00
|
|
|
# None for now
|
|
|
|
override = {
|
|
|
|
'window': None,
|
|
|
|
'screen': None,
|
|
|
|
'area': None,
|
|
|
|
'region': None,
|
|
|
|
'edit_object': None,
|
|
|
|
'scene': scene}
|
|
|
|
return override
|
2016-04-08 12:12:21 +02:00
|
|
|
|
2016-02-08 17:28:05 +01:00
|
|
|
# Transform Blender data into game data
|
2015-10-30 13:23:09 +01:00
|
|
|
def exportGameData():
|
2016-02-08 17:28:05 +01:00
|
|
|
shader_references = []
|
|
|
|
asset_references = []
|
|
|
|
|
2016-03-15 11:29:53 +01:00
|
|
|
# Build nodes
|
|
|
|
# TODO: cache
|
2016-02-08 17:28:05 +01:00
|
|
|
nodes_logic.buildNodeTrees()
|
2016-03-15 11:29:53 +01:00
|
|
|
nodes_pipeline.buildNodeTrees(shader_references, asset_references)
|
2016-03-17 19:29:53 +01:00
|
|
|
# TODO: Have to build nodes everytime to collect env map resources, should be cached
|
|
|
|
nodes_world.buildNodeTrees(shader_references, asset_references)
|
2015-10-30 13:23:09 +01:00
|
|
|
|
2016-02-08 17:28:05 +01:00
|
|
|
# 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
|
2015-10-30 13:23:09 +01:00
|
|
|
|
2016-02-08 17:28:05 +01:00
|
|
|
# Export scene data
|
|
|
|
for scene in bpy.data.scenes:
|
|
|
|
if scene.name[0] != '.': # Skip hidden scenes
|
2016-04-08 12:12:21 +02:00
|
|
|
bpy.ops.export_scene.armory(
|
|
|
|
get_export_scene_override(scene),
|
|
|
|
filepath='Assets/generated/' + scene.name + '.json')
|
2016-02-08 17:28:05 +01:00
|
|
|
shader_references += ArmoryExporter.shader_references
|
|
|
|
asset_references += ArmoryExporter.asset_references
|
2015-10-30 13:23:09 +01:00
|
|
|
|
2016-02-08 17:28:05 +01:00
|
|
|
# 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
|
|
|
|
|
|
|
|
# Write khafile.js
|
|
|
|
write_data.write_khafilejs(shader_references, asset_references)
|
2015-12-07 20:04:23 +01:00
|
|
|
|
2016-02-08 17:28:05 +01:00
|
|
|
# Write Main.hx
|
|
|
|
write_data.write_main()
|
2016-01-17 22:31:31 +01:00
|
|
|
|
2016-02-08 17:28:05 +01:00
|
|
|
def buildProject(self, build_type=0):
|
|
|
|
# Save blend
|
|
|
|
bpy.ops.wm.save_mainfile()
|
2015-10-30 13:23:09 +01:00
|
|
|
|
2016-02-08 17:28:05 +01:00
|
|
|
# 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
|
|
|
|
|
2016-02-08 21:31:53 +01:00
|
|
|
# Get paths
|
|
|
|
haxelib_path = "haxelib"
|
|
|
|
if platform.system() == 'Darwin':
|
|
|
|
haxelib_path = "/usr/local/bin/haxelib"
|
|
|
|
|
2016-02-13 14:22:04 +01:00
|
|
|
#prefix = haxelib_path + " run kha "
|
2016-02-08 21:31:53 +01:00
|
|
|
|
|
|
|
output = subprocess.check_output([haxelib_path + " path cyclesgame"], shell=True)
|
|
|
|
output = str(output).split("\\n")[0].split("'")[1]
|
|
|
|
scripts_path = output[:-8] + "blender/"
|
|
|
|
raw_path = output[:-8] + "raw/"
|
|
|
|
|
2016-02-08 17:28:05 +01:00
|
|
|
# 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)
|
2016-02-08 21:31:53 +01:00
|
|
|
|
2016-03-17 19:29:53 +01:00
|
|
|
# Compile path tracer shaders
|
|
|
|
if len(bpy.data.cameras) > 0 and bpy.data.cameras[0].pipeline_path == 'pathtrace_pipeline':
|
|
|
|
path_tracer.compile(raw_path + 'pt_trace_pass/pt_trace_pass.frag.glsl')
|
|
|
|
|
2016-02-08 21:31:53 +01:00
|
|
|
# Compile shaders if needed
|
|
|
|
if os.path.isdir("compiled") == False:
|
|
|
|
os.chdir(raw_path)
|
|
|
|
call(["python", "compile.py"])
|
|
|
|
os.chdir(fp)
|
2015-10-30 13:23:09 +01:00
|
|
|
|
2016-02-08 17:28:05 +01:00
|
|
|
# Export
|
|
|
|
exportGameData()
|
|
|
|
|
|
|
|
# Set build command
|
|
|
|
if (bpy.data.worlds[0]['CGProjectTarget'] == 0):
|
|
|
|
bashCommand = "-t html5"
|
|
|
|
elif (bpy.data.worlds[0]['CGProjectTarget'] == 1):
|
|
|
|
bashCommand = "-t windows"
|
|
|
|
elif (bpy.data.worlds[0]['CGProjectTarget'] == 2):
|
|
|
|
bashCommand = "-t osx"
|
|
|
|
elif (bpy.data.worlds[0]['CGProjectTarget'] == 3):
|
|
|
|
bashCommand = "-t linux"
|
|
|
|
elif (bpy.data.worlds[0]['CGProjectTarget'] == 4):
|
|
|
|
bashCommand = "-t ios"
|
|
|
|
elif (bpy.data.worlds[0]['CGProjectTarget'] == 5):
|
|
|
|
bashCommand = "-t android-native"
|
|
|
|
|
|
|
|
# Build
|
|
|
|
blender_path = bpy.app.binary_path
|
|
|
|
blend_path = bpy.data.filepath
|
2016-03-15 11:29:53 +01:00
|
|
|
# p = subprocess.Popen([blender_path, blend_path, '-b', '-P', scripts_path + 'lib/build.py', '--', bashCommand, str(build_type), str(bpy.data.worlds[0]['CGProjectTarget'])])
|
|
|
|
# atexit.register(p.terminate)
|
2016-02-08 17:28:05 +01:00
|
|
|
|
|
|
|
self.report({'INFO'}, "Building, see console...")
|
2015-10-30 13:23:09 +01:00
|
|
|
|
|
|
|
def cleanProject(self):
|
2016-02-08 17:28:05 +01:00
|
|
|
# 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 data
|
|
|
|
if os.path.isdir("build"):
|
|
|
|
shutil.rmtree('build')
|
2015-10-30 13:23:09 +01:00
|
|
|
|
2016-02-08 17:28:05 +01:00
|
|
|
# Remove generated data
|
|
|
|
if os.path.isdir("Assets/generated"):
|
|
|
|
shutil.rmtree('Assets/generated')
|
2016-01-17 22:31:31 +01:00
|
|
|
|
2016-02-08 17:28:05 +01:00
|
|
|
# Remove compiled nodes
|
|
|
|
nodes_path = "Sources/" + bpy.data.worlds[0].CGProjectPackage.replace(".", "/") + "/node/"
|
|
|
|
if os.path.isdir(nodes_path):
|
|
|
|
shutil.rmtree(nodes_path)
|
2015-10-30 13:23:09 +01:00
|
|
|
|
2016-02-08 17:28:05 +01:00
|
|
|
self.report({'INFO'}, "Done")
|
2015-10-30 13:23:09 +01:00
|
|
|
|
|
|
|
# Play
|
|
|
|
class OBJECT_OT_PLAYButton(bpy.types.Operator):
|
2016-02-08 17:28:05 +01:00
|
|
|
bl_idname = "cg.play"
|
|
|
|
bl_label = "Play"
|
2015-10-30 13:23:09 +01:00
|
|
|
|
2016-02-08 17:28:05 +01:00
|
|
|
def execute(self, context):
|
|
|
|
buildProject(self, 1)
|
|
|
|
return{'FINISHED'}
|
2015-10-30 13:23:09 +01:00
|
|
|
|
|
|
|
# Build
|
|
|
|
class OBJECT_OT_BUILDButton(bpy.types.Operator):
|
2016-02-08 17:28:05 +01:00
|
|
|
bl_idname = "cg.build"
|
|
|
|
bl_label = "Build"
|
2015-10-30 13:23:09 +01:00
|
|
|
|
2016-02-08 17:28:05 +01:00
|
|
|
def execute(self, context):
|
|
|
|
buildProject(self, 0)
|
|
|
|
return{'FINISHED'}
|
2015-10-30 13:23:09 +01:00
|
|
|
|
|
|
|
# Open project folder
|
|
|
|
class OBJECT_OT_FOLDERButton(bpy.types.Operator):
|
2016-02-08 17:28:05 +01:00
|
|
|
bl_idname = "cg.folder"
|
|
|
|
bl_label = "Folder"
|
2015-10-30 13:23:09 +01:00
|
|
|
|
2016-02-08 17:28:05 +01:00
|
|
|
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'}
|
|
|
|
|
2015-10-30 13:23:09 +01:00
|
|
|
# Clean project
|
|
|
|
class OBJECT_OT_CLEANButton(bpy.types.Operator):
|
2016-02-08 17:28:05 +01:00
|
|
|
bl_idname = "cg.clean"
|
|
|
|
bl_label = "Clean"
|
2015-10-30 13:23:09 +01:00
|
|
|
|
2016-02-08 17:28:05 +01:00
|
|
|
def execute(self, context):
|
|
|
|
cleanProject(self)
|
|
|
|
return{'FINISHED'}
|
2015-10-30 13:23:09 +01:00
|
|
|
|
|
|
|
# Registration
|
2015-12-07 20:04:23 +01:00
|
|
|
def register():
|
2016-02-08 17:28:05 +01:00
|
|
|
bpy.utils.register_module(__name__)
|
|
|
|
# Store properties in world
|
|
|
|
initWorldProperties()
|
2015-12-07 20:04:23 +01:00
|
|
|
|
|
|
|
def unregister():
|
2016-02-08 17:28:05 +01:00
|
|
|
bpy.utils.unregister_module(__name__)
|