Begin refactoring

This commit is contained in:
Lubos Lenco 2015-10-30 13:23:09 +01:00
parent 84dff3ccb0
commit ee9677135c
13 changed files with 4017 additions and 1 deletions

3
README.md Normal file → Executable file
View File

@ -1 +1,2 @@
# zblend
zblend
==============

0
blender/__init__.py Executable file
View File

194
blender/assets.py Executable file
View File

@ -0,0 +1,194 @@
import shutil
import bpy
import os
import json
from bpy.types import Menu, Panel, UIList
from bpy.props import *
class ListItem(bpy.types.PropertyGroup):
# Group of properties representing an item in the list
name = bpy.props.StringProperty(
name="Name",
description="A name for this item",
default="Untitled")
enabled_prop = bpy.props.BoolProperty(
name="",
description="A name for this item",
default=True)
type_prop = bpy.props.EnumProperty(
items = [('Image', 'Image', 'Image'),
('Blob', 'Blob', 'Blob'),
('Sound', 'Sound', 'Sound'),
('Music', 'Music', 'Music'),
('Font', 'Font', 'Font')],
name = "Type")
size_prop = bpy.props.IntProperty(
name="Size",
description="A name for this item",
default=0)
bpy.utils.register_class(ListItem)
class MY_UL_List(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
# We could write some code to decide which icon to use here...
custom_icon = 'OBJECT_DATAMODE'
# Make sure your code supports all 3 layout types
if self.layout_type in {'DEFAULT', 'COMPACT'}:
layout.prop(item, "enabled_prop")
#layout.label(item.name, icon = custom_icon)
layout.prop(item, "name", text="", emboss=False, icon=custom_icon)
elif self.layout_type in {'GRID'}:
layout.alignment = 'CENTER'
layout.label("", icon = custom_icon)
bpy.utils.register_class(MY_UL_List)
def initWorldProperties():
bpy.types.World.my_list = bpy.props.CollectionProperty(type = ListItem)
bpy.types.World.list_index = bpy.props.IntProperty(name = "Index for my_list", default = 0)
initWorldProperties()
class LIST_OT_NewItem(bpy.types.Operator):
# Add a new item to the list
bl_idname = "my_list.new_item"
bl_label = "Add a new item"
def execute(self, context):
bpy.data.worlds[0].my_list.add()
return{'FINISHED'}
class LIST_OT_DeleteItem(bpy.types.Operator):
# Delete the selected item from the list
bl_idname = "my_list.delete_item"
bl_label = "Deletes an item"
@classmethod
def poll(self, context):
""" Enable if there's something in the list """
return len(bpy.data.worlds[0].my_list) > 0
def execute(self, context):
list = bpy.data.worlds[0].my_list
index = bpy.data.worlds[0].list_index
list.remove(index)
if index > 0:
index = index - 1
bpy.data.worlds[0].list_index = index
return{'FINISHED'}
class LIST_OT_MoveItem(bpy.types.Operator):
# Move an item in the list
bl_idname = "my_list.move_item"
bl_label = "Move an item in the list"
direction = bpy.props.EnumProperty(
items=(
('UP', 'Up', ""),
('DOWN', 'Down', ""),))
@classmethod
def poll(self, context):
""" Enable if there's something in the list. """
return len(bpy.data.worlds[0].my_list) > 0
def move_index(self):
# Move index of an item render queue while clamping it
index = bpy.data.worlds[0].list_index
list_length = len(bpy.data.worlds[0].my_list) - 1 # (index starts at 0)
new_index = 0
if self.direction == 'UP':
new_index = index - 1
elif self.direction == 'DOWN':
new_index = index + 1
new_index = max(0, min(new_index, list_length))
index = new_index
def execute(self, context):
list = bpy.data.worlds[0].my_list
index = bpy.data.worlds[0].list_index
if self.direction == 'DOWN':
neighbor = index + 1
#queue.move(index,neighbor)
self.move_index()
elif self.direction == 'UP':
neighbor = index - 1
#queue.move(neighbor, index)
self.move_index()
else:
return{'CANCELLED'}
return{'FINISHED'}
#
# Menu in tools region
#
class ToolsAssetsPanel(bpy.types.Panel):
bl_label = "zblend_assets"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "render"
def draw(self, context):
layout = self.layout
scene = bpy.data.worlds[0]
rows = 2
if len(scene.my_list) > 1:
rows = 4
row = layout.row()
row.template_list("MY_UL_List", "The_List", scene, "my_list", scene, "list_index", rows=rows)
col = row.column(align=True)
col.operator("my_list.new_item", icon='ZOOMIN', text="")
col.operator("my_list.delete_item", icon='ZOOMOUT', text="")#.all = False
if len(scene.my_list) > 1:
col.separator()
col.operator("my_list.move_item", icon='TRIA_UP', text="").direction = 'UP'
col.operator("my_list.move_item", icon='TRIA_DOWN', text="").direction = 'DOWN'
if scene.list_index >= 0 and len(scene.my_list) > 0:
item = scene.my_list[scene.list_index]
#row = layout.row()
#row.prop(item, "name")
row = layout.row()
row.prop(item, "type_prop")
if item.type_prop == 'Font':
row = layout.row()
row.prop(item, "size_prop")
#
# Registration
#
bpy.utils.register_module(__name__)

53
blender/build.py Executable file
View File

@ -0,0 +1,53 @@
import os
import webbrowser
import sys
import bpy
import shutil
def runProject(fp, target, name):
os.chdir(fp)
# OSX
if (target == '0'):
bashCommand = "xcodebuild -project 'build/osx-build/" + name + ".xcodeproj' && open 'build/osx-build/build/Release/" + name + ".app/Contents/MacOS/" + name + "'"
os.system(bashCommand)
# HTML5
elif (target == '3'):
webbrowser.open("http://127.0.0.1:8000/build/html5",new=2)
def openProject(fp, target, name):
os.chdir(fp)
# OSX
if (target == '0'):
bashCommand = "open -a Xcode.app 'build/osx-build/" + name + ".xcodeproj'"
os.system(bashCommand)
def build():
argv = sys.argv
argv = argv[argv.index("--") + 1:] # Get all args after "--"
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)
bashCommand = argv[0]
build_type = argv[1]
target = argv[2]
os.system(bashCommand)
# Copy ammo.js if necessary
if (target == '3'):
if not os.path.isfile('build/html5/ammo.js'):
shutil.copy('Libraries/haxebullet/js/ammo/ammo.js', 'build/html5')
if build_type == '2':
openProject(fp, target, name)
elif build_type == '1':
runProject(fp, target, name)
print("Build done!")
build()

81
blender/fetch.py Executable file
View File

@ -0,0 +1,81 @@
import os
import bpy
def fetch():
s = bpy.data.filepath.split(os.path.sep)
name = s.pop()
name = name.split(".")
name = name[0]
fp = os.path.sep.join(s)
# Update scripts
os.chdir(fp + "/Libraries/zblend/blender")
os.system("git pull")
# Clone kha
#self.report({'INFO'}, "Fetching Kha...")
os.chdir(fp)
if not os.path.exists('Kha'):
os.system("git clone --depth=1 --recursive https://github.com/ktxsoftware/Kha")
os.chdir(fp + "/Kha")
os.system("git pull && git submodule foreach --recursive git checkout master && git submodule foreach --recursive git pull origin master")
# Create sources directories
os.chdir(fp)
if not os.path.exists('Sources/Shaders'):
os.makedirs('Sources/Shaders')
if not os.path.exists('Libraries/zblend/Sources'):
os.makedirs('Libraries/zblend/Sources')
if not os.path.exists('Libraries/dependencies'):
os.makedirs('Libraries/dependencies')
if not os.path.exists('Assets'):
os.makedirs('Assets')
# Clone dependencies
#self.report({'INFO'}, "Fetching dependencies...")
os.chdir(fp + "/Libraries/dependencies")
if not os.path.exists('Sources'):
os.system("git clone --depth=1 https://github.com/luboslenco/zblend_dependencies Sources")
os.chdir(fp + "/Libraries/dependencies/Sources")
os.system("git pull")
# Clone shaders
#self.report({'INFO'}, "Fetching shaders...")
os.chdir(fp + "/Libraries/zblend/Sources")
if not os.path.exists('Shaders'):
os.system("git clone --depth=1 https://github.com/luboslenco/zblend_shaders Shaders")
os.chdir(fp + "/Libraries/zblend/Sources/Shaders")
os.system("git pull")
# Clone oimo
os.chdir(fp + "/Libraries")
if not os.path.exists('oimo'):
os.system("git clone --depth=1 https://github.com/luboslenco/oimo oimo")
os.chdir(fp + "/Libraries/oimo")
os.system("git pull")
# Clone haxebullet
#self.report({'INFO'}, "Fetching physics...")
os.chdir(fp + "/Libraries")
if not os.path.exists('haxebullet'):
os.system("git clone --depth=1 https://github.com/luboslenco/haxebullet haxebullet")
os.chdir(fp + "/Libraries/haxebullet")
os.system("git pull")
# Clone zblend
#self.report({'INFO'}, "Fetching zblend...")
os.chdir(fp + "/Libraries/zblend/Sources")
if not os.path.exists('zblend'):
os.system("git clone --depth=1 https://github.com/luboslenco/zblend")
os.chdir(fp + "/Libraries/zblend/Sources/zblend")
os.system("git pull")
print("Fetch complete!")
fetch()

173
blender/libraries.py Executable file
View File

@ -0,0 +1,173 @@
import shutil
import bpy
import os
import json
from bpy.types import Menu, Panel, UIList
from bpy.props import *
class LibListItem(bpy.types.PropertyGroup):
# Group of properties representing an item in the list
name = bpy.props.StringProperty(
name="Name",
description="A name for this item",
default="Untitled")
enabled_prop = bpy.props.BoolProperty(
name="",
description="A name for this item",
default=True)
bpy.utils.register_class(LibListItem)
class MY_UL_LibList(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
# We could write some code to decide which icon to use here...
custom_icon = 'OBJECT_DATAMODE'
# Make sure your code supports all 3 layout types
if self.layout_type in {'DEFAULT', 'COMPACT'}:
layout.prop(item, "enabled_prop")
#layout.label(item.name, icon = custom_icon)
layout.prop(item, "name", text="", emboss=False, icon=custom_icon)
elif self.layout_type in {'GRID'}:
layout.alignment = 'CENTER'
layout.label("", icon = custom_icon)
bpy.utils.register_class(MY_UL_LibList)
def initWorldProperties():
bpy.types.World.my_liblist = bpy.props.CollectionProperty(type = LibListItem)
bpy.types.World.liblist_index = bpy.props.IntProperty(name = "Index for my_liblist", default = 0)
initWorldProperties()
class LIBLIST_OT_NewItem(bpy.types.Operator):
# Add a new item to the list
bl_idname = "my_liblist.new_item"
bl_label = "Add a new item"
def execute(self, context):
bpy.data.worlds[0].my_liblist.add()
return{'FINISHED'}
class LIBLIST_OT_DeleteItem(bpy.types.Operator):
# Delete the selected item from the list
bl_idname = "my_liblist.delete_item"
bl_label = "Deletes an item"
@classmethod
def poll(self, context):
""" Enable if there's something in the list """
return len(bpy.data.worlds[0].my_liblist) > 0
def execute(self, context):
list = bpy.data.worlds[0].my_liblist
index = bpy.data.worlds[0].liblist_index
list.remove(index)
if index > 0:
index = index - 1
bpy.data.worlds[0].liblist_index = index
return{'FINISHED'}
class LIST_OT_MoveItem(bpy.types.Operator):
# Move an item in the list
bl_idname = "my_liblist.move_item"
bl_label = "Move an item in the list"
direction = bpy.props.EnumProperty(
items=(
('UP', 'Up', ""),
('DOWN', 'Down', ""),))
@classmethod
def poll(self, context):
""" Enable if there's something in the list. """
return len(bpy.data.worlds[0].my_liblist) > 0
def move_index(self):
# Move index of an item render queue while clamping it
index = bpy.data.worlds[0].liblist_index
list_length = len(bpy.data.worlds[0].my_liblist) - 1 # (index starts at 0)
new_index = 0
if self.direction == 'UP':
new_index = index - 1
elif self.direction == 'DOWN':
new_index = index + 1
new_index = max(0, min(new_index, list_length))
index = new_index
def execute(self, context):
list = bpy.data.worlds[0].my_liblist
index = bpy.data.worlds[0].liblist_index
if self.direction == 'DOWN':
neighbor = index + 1
#queue.move(index,neighbor)
self.move_index()
elif self.direction == 'UP':
neighbor = index - 1
#queue.move(neighbor, index)
self.move_index()
else:
return{'CANCELLED'}
return{'FINISHED'}
#
# Menu in tools region
#
class ToolsLibrariesPanel(bpy.types.Panel):
bl_label = "zblend_libraries"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "render"
def draw(self, context):
layout = self.layout
scene = bpy.data.worlds[0]
rows = 2
if len(scene.my_liblist) > 1:
rows = 4
row = layout.row()
row.template_list("MY_UL_LibList", "The_List", scene, "my_liblist", scene, "liblist_index", rows=rows)
col = row.column(align=True)
col.operator("my_liblist.new_item", icon='ZOOMIN', text="")
col.operator("my_liblist.delete_item", icon='ZOOMOUT', text="")#.all = False
if len(scene.my_liblist) > 1:
col.separator()
col.operator("my_liblist.move_item", icon='TRIA_UP', text="").direction = 'UP'
col.operator("my_liblist.move_item", icon='TRIA_DOWN', text="").direction = 'DOWN'
#if scene.liblist_index >= 0 and len(scene.my_liblist) > 0:
#item = scene.my_liblist[scene.liblist_index]
#row = layout.row()
#row.prop(item, "name")
#row = layout.row()
#row.prop(item, "type_prop")
#
# Registration
#
bpy.utils.register_module(__name__)

226
blender/nodes.py Executable file
View File

@ -0,0 +1,226 @@
import bpy
from bpy.types import NodeTree, Node, NodeSocket
from bpy.props import *
# Implementation of custom nodes from Python
# Derived from the NodeTree base type, similar to Menu, Operator, Panel, etc.
class MyCustomTree(NodeTree):
# Description string
'''ZBlend logic nodes'''
# Optional identifier string. If not explicitly defined, the python class name is used.
bl_idname = 'CustomTreeType'
# Label for nice name display
bl_label = 'ZBlend Node Tree'
# Icon identifier
# NOTE: If no icon is defined, the node tree will not show up in the editor header!
# 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
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.
class MyCustomTreeNode:
@classmethod
def poll(cls, ntree):
return ntree.bl_idname == 'CustomTreeType'
# Derived from the Node base type.
class TransformNode(Node, MyCustomTreeNode):
# 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
# a purely internal Python method and unknown to the node system!
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.
class TimeNode(Node, MyCustomTreeNode):
# 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!")
class VectorNode(Node, MyCustomTreeNode):
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()
class ScaleValueNode(Node, MyCustomTreeNode):
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()
class SineNode(Node, MyCustomTreeNode):
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):
return context.space_data.tree_type == 'CustomTreeType'
# 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"),
]),
]
classes = [MyCustomTree, TransformNode, TimeNode, VectorNode, ScaleValueNode, SineNode]
def register():
for c in classes:
bpy.utils.register_class(c)
try:
# nodeitems_utils.unregister_node_categories("CUSTOM_NODES")
nodeitems_utils.register_node_categories("CUSTOM_NODES", node_categories)
except:
pass
def unregister():
for c in classes:
bpy.utils.unregister_class(c)
nodeitems_utils.unregister_node_categories("CUSTOM_NODES")
if __name__ == "__main__":
register()

780
blender/project.py Executable file
View File

@ -0,0 +1,780 @@
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['TargetZblendVersion'] = "0.1.0"
wrd['TargetEnum'] = 3
wrd['TargetRenderer'] = 0
wrd['TargetProjectName'] = "my_project"
wrd['TargetProjectPackage'] = "my_project"
wrd['TargetProjectWidth'] = 1136
wrd['TargetProjectHeight'] = 640
wrd['TargetProjectScale'] = 1.0
wrd['TargetProjectOrient'] = 0
wrd['TargetScene'] = bpy.data.scenes[0].name
wrd['TargetGravity'] = bpy.data.scenes[0].name
wrd['TargetClear'] = bpy.data.worlds[0].name
wrd['TargetFog'] = (False)
wrd['TargetFogColor'] = (0.5, 0.5, 0.7, 1.0)
wrd['TargetFogDensity'] = (0.04)
wrd['TargetShadowMapping'] = (False)
wrd['TargetShadowMapSize'] = 1024
wrd['TargetShading'] = 0
wrd['TargetShader'] = 0
wrd['TargetAA'] = 1
wrd['TargetPhysics'] = 1
wrd['TargetSSAO'] = (False)
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.TargetZblendVersion = StringProperty(name = "ZblendVersion")
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.TargetRenderer = EnumProperty(
items = [('OGL', 'OGL', 'OGL'),
('D3D9', 'D3D9', 'D3D9'),
('D3D11', 'D3D11', 'D3D11')],
name = "Renderer")
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.TargetProjectScale = FloatProperty(name = "Scale", default=1.0)
bpy.types.World.TargetProjectOrient = EnumProperty(
items = [('Portrait', 'Portrait', 'Portrait'),
('Landscape', 'Landscape', 'Landscape')],
name = "Orient")
bpy.types.World.TargetScene = StringProperty(name = "Scene")
bpy.types.World.TargetGravity = StringProperty(name = "Gravity")
bpy.types.World.TargetClear = StringProperty(name = "Clear")
bpy.types.World.TargetFog = BoolProperty(name = "Fog")
bpy.types.World.TargetFogColor = FloatVectorProperty(name = "Fog Color", default=[0.5,0.5,0.7,1], size=4, subtype="COLOR", min=0, max=1)
bpy.types.World.TargetFogDensity = FloatProperty(name = "Fog Density", min=0, max=1)
bpy.types.World.TargetShadowMapping = BoolProperty(name = "Shadow mapping")
bpy.types.World.TargetShadowMapSize = IntProperty(name = "Shadow map size")
bpy.types.World.TargetShading = EnumProperty(
items = [('Forward', 'Forward', 'Forward'),
('Deferred', 'Deferred', 'Deferred')],
name = "Shading")
bpy.types.World.TargetShader = EnumProperty(
items = [('Physically based', 'Physically based', 'Physically based'),
('Flat', 'Flat', 'Flat'),
('Unlit', 'Unlit', 'Unlit')],
name = "Shader")
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.TargetSSAO = BoolProperty(name = "SSAO")
bpy.types.World.TargetAutoBuildNodes = BoolProperty(name = "Auto-build nodes")
bpy.types.World.TargetMinimize = BoolProperty(name = "Minimize")
# Default settings
# todo: check version
if not 'TargetZblendVersion' 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 for now
initWorldProperties()
# Info panel play
def draw_play_item(self, context):
layout = self.layout
layout.operator("zblend.play")
# Menu in tools region
class ToolsPanel(bpy.types.Panel):
bl_label = "zblend_project"
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')
row = layout.row()
row.active = False
row.prop(wrd, 'TargetProjectScale')
layout.prop_search(wrd, "TargetScene", bpy.data, "scenes", "Scene")
layout.prop(wrd, 'TargetEnum')
if wrd['TargetEnum'] == 4 or wrd['TargetEnum'] == 5:
layout.prop(wrd, 'TargetProjectOrient')
row = layout.row()
row.active = False
row.prop(wrd, 'TargetRenderer')
row = layout.row(align=True)
row.alignment = 'EXPAND'
row.operator("zblend.play")
row.operator("zblend.build")
row.operator("zblend.project")
row = layout.row(align=True)
row.alignment = 'EXPAND'
row.operator("zblend.folder")
row.operator("zblend.clean")
layout.prop_search(wrd, "TargetGravity", bpy.data, "scenes", "Gravity")
layout.prop_search(wrd, "TargetClear", bpy.data, "worlds", "Clear Color")
layout.prop(wrd, 'TargetFog')
if wrd['TargetFog'] == True:
layout.prop(wrd, 'TargetFogColor')
layout.prop(wrd, 'TargetFogDensity')
layout.prop(wrd, 'TargetShadowMapping')
if wrd['TargetShadowMapping'] == True:
layout.prop(wrd, 'TargetShadowMapSize')
layout.prop(wrd, 'TargetShading')
layout.prop(wrd, 'TargetShader')
layout.prop(wrd, 'TargetAA')
layout.prop(wrd, 'TargetPhysics')
layout.prop(wrd, 'TargetSSAO')
row = layout.row()
row.prop(wrd, 'TargetAutoBuildNodes')
if wrd['TargetAutoBuildNodes'] == False:
row.operator("zblend.buildnodes")
layout.prop(wrd, 'TargetMinimize')
layout.operator("zblend.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.zblend({"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 = 2
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 = ["zblend", "haxebullet", "dependencies"]
# 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.orient = bpy.data.worlds[0]['TargetProjectOrient']
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.fog = bpy.data.worlds[0]['TargetFog']
data.fogColor = [bpy.data.worlds[0]['TargetFogColor'][0], bpy.data.worlds[0]['TargetFogColor'][1], bpy.data.worlds[0]['TargetFogColor'][2], bpy.data.worlds[0]['TargetFogColor'][3]]
data.fogDensity = bpy.data.worlds[0]['TargetFogDensity']
data.shadowMapping = bpy.data.worlds[0]['TargetShadowMapping']
data.shadowMapSize = bpy.data.worlds[0]['TargetShadowMapSize']
data.physics = bpy.data.worlds[0]['TargetPhysics']
data.ssao = bpy.data.worlds[0]['TargetSSAO']
with open('Assets/data.json', 'w') as f:
f.write(data.to_JSON())
# Write Main.hx
# TODO: move to separate file
#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() {
CompileTime.importPackage('zblend.trait');
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() {
var starter = new kha.Starter();
starter.start(new zblend.Root("ZBlend", "room1", Game));
}
}
class Game {
public function new() {
zblend.Root.setScene(zblend.Root.gameData.scene);
}
}
""")
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
#self.report({'INFO'}, "Exporting game data...")
exportGameData()
# Set build command
if (bpy.data.worlds[0]['TargetEnum'] == 0):
bashCommand = "Kha/make.js -t osx"
elif (bpy.data.worlds[0]['TargetEnum'] == 1):
bashCommand = "Kha/make.js -t windows"
elif (bpy.data.worlds[0]['TargetEnum'] == 2):
bashCommand = "Kha/make.js -t linux"
elif (bpy.data.worlds[0]['TargetEnum'] == 3):
bashCommand = "Kha/make.js -t html5"
elif (bpy.data.worlds[0]['TargetEnum'] == 4):
bashCommand = "Kha/make.js -t ios"
elif (bpy.data.worlds[0]['TargetEnum'] == 5):
bashCommand = "Kha/make.js -t android"
# Build
prefix = "node "
if (platform.system() == "Darwin"):
prefix = "/usr/local/bin/node "
elif (platform.system() == "Linux"):
prefix = "nodejs "
#p = Process(target=build_process, args=(prefix + bashCommand, open, run, fp, bpy.data.worlds[0]['TargetEnum'], name,))
#p.start()
#atexit.register(p.terminate)
blender_path = bpy.app.binary_path
blend_path = bpy.data.filepath
p = subprocess.Popen([blender_path, blend_path, '-b', '-P', fp + '/Libraries/zblend/blender/zblend_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'}, "Clean done")
# Play
class OBJECT_OT_PLAYButton(bpy.types.Operator):
bl_idname = "zblend.play"
bl_label = "Play"
def execute(self, context):
buildProject(self, 1)
return{'FINISHED'}
# Build
class OBJECT_OT_BUILDButton(bpy.types.Operator):
bl_idname = "zblend.build"
bl_label = "Build"
def execute(self, context):
buildProject(self, 0)
return{'FINISHED'}
# Open project
class OBJECT_OT_PROJECTButton(bpy.types.Operator):
bl_idname = "zblend.project"
bl_label = "Project"
def execute(self, context):
buildProject(self, 2)
return{'FINISHED'}
# Open project folder
class OBJECT_OT_FOLDERButton(bpy.types.Operator):
bl_idname = "zblend.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 = "zblend.clean"
bl_label = "Clean"
def execute(self, context):
cleanProject(self)
return{'FINISHED'}
# Build nodes
class OBJECT_OT_BUILDNODESButton(bpy.types.Operator):
bl_idname = "zblend.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 = "zblend.defaultsettings"
bl_label = "Default Settings"
def execute(self, context):
defaultSettings()
self.report({'INFO'}, "Defaults set")
return{'FINISHED'}
def buildNodeTrees():
# Set dir
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 zblend.node.*;\n\n')
f.write('class ' + node_group_name + ' extends zblend.trait.NodeExecutor {\n\n')
f.write('\tpublic function new() { super(); }\n\n')
f.write('\toverride function onItemAdd() {\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 = owner.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__)

1962
blender/scene.py Executable file

File diff suppressed because it is too large Load Diff

14
blender/server.py Executable file
View File

@ -0,0 +1,14 @@
import http.server
import socketserver
# Can't use multiprocessing on Windows
#p = Process(target=run_server)
#p.start()
#atexit.register(p.terminate)
def run_server():
Handler = http.server.SimpleHTTPRequestHandler
httpd = socketserver.TCPServer(("", 8080), Handler)
httpd.serve_forever()
run_server()

396
blender/traits.py Executable file
View File

@ -0,0 +1,396 @@
import shutil
import bpy
import os
import json
from zblend_traits_animation import ListAnimationTraitItem
from bpy.types import Menu, Panel, UIList
from bpy.props import *
class ListTraitItem(bpy.types.PropertyGroup):
# Group of properties representing an item in the list
name = bpy.props.StringProperty(
name="Name",
description="A name for this item",
default="Untitled")
enabled_prop = bpy.props.BoolProperty(
name="",
description="A name for this item",
default=True)
type_prop = bpy.props.EnumProperty(
items = [('Script', 'Script', 'Script'),
('Nodes', 'Nodes', 'Nodes'),
('Scene Instance', 'Scene Instance', 'Scene Instance'),
('Animation', 'Animation', 'Animation'),
('Camera', 'Camera', 'Camera'),
('Light', 'Light', 'Light'),
('Rigid Body', 'Rigid Body', 'Rigid Body'),
('Mesh Renderer', 'Mesh Renderer', 'Mesh Renderer'),
('Billboard Renderer', 'Billboard Renderer', 'Billboard Renderer'),
('Particles Renderer', 'Particles Renderer', 'Particles Renderer'),
('Skydome Renderer', 'Skydome Renderer', 'Skydome Renderer'),
('Custom Renderer', 'Custom Renderer', 'Custom Renderer')
],
name = "Type")
default_material_prop = bpy.props.BoolProperty(
name="Default Material",
description="A name for this item",
default=True)
material_prop = bpy.props.StringProperty(
name="Material",
description="A name for this item",
default="")
lighting_prop = bpy.props.BoolProperty(
name="Lighting",
default=True)
cast_shadow_prop = bpy.props.BoolProperty(
name="Cast Shadow",
default=True)
receive_shadow_prop = bpy.props.BoolProperty(
name="Receive Shadow",
default=True)
shader_prop = bpy.props.StringProperty(
name="Shader",
description="A name for this item",
default="")
data_prop = bpy.props.StringProperty(
name="Data",
description="A name for this item",
default="")
camera_link_prop = bpy.props.StringProperty(
name="Camera",
description="A name for this item",
default="Camera")
light_link_prop = bpy.props.StringProperty(
name="Light",
description="A name for this item",
default="Lamp")
default_body_prop = bpy.props.BoolProperty(
name="Default Body",
description="A name for this item",
default=True)
body_link_prop = bpy.props.StringProperty(
name="Body",
description="A name for this item",
default="")
custom_shape_prop = bpy.props.BoolProperty(
name="Custom Shape",
description="A name for this item",
default=False)
custom_shape_type_prop = bpy.props.EnumProperty(
items = [('Terrain', 'Terrain', 'Terrain'),
('Static Mesh', 'Static Mesh', 'Static Mesh')
],
name = "Shape")
shape_size_scale_prop = bpy.props.FloatVectorProperty(
name = "Size Scale",
default=[1,1,1],
size=3, min=0, max=10)
scene_prop = bpy.props.StringProperty(
name="Scene",
description="A name for this item",
default="")
class_name_prop = bpy.props.StringProperty(
name="Class",
description="A name for this item",
default="Untitled")
nodes_name_prop = bpy.props.StringProperty(
name="Nodes",
description="A name for this item",
default="")
start_track_name_prop = bpy.props.StringProperty(
name="Start Track",
description="A name for this item",
default="")
bpy.utils.register_class(ListTraitItem)
class MY_UL_TraitList(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
# We could write some code to decide which icon to use here...
custom_icon = 'OBJECT_DATAMODE'
# Make sure your code supports all 3 layout types
if self.layout_type in {'DEFAULT', 'COMPACT'}:
layout.prop(item, "enabled_prop")
layout.label(item.name, icon = custom_icon)
elif self.layout_type in {'GRID'}:
layout.alignment = 'CENTER'
layout.label("", icon = custom_icon)
bpy.utils.register_class(MY_UL_TraitList)
def initObjectProperties():
bpy.types.Object.my_traitlist = bpy.props.CollectionProperty(type = ListTraitItem)
bpy.types.Object.traitlist_index = bpy.props.IntProperty(name = "Index for my_list", default = 0)
initObjectProperties()
class LIST_OT_TraitNewItem(bpy.types.Operator):
# Add a new item to the list
bl_idname = "my_traitlist.new_item"
bl_label = "Add a new item"
def execute(self, context):
bpy.context.object.my_traitlist.add()
bpy.context.object.traitlist_index += 1
return{'FINISHED'}
class LIST_OT_TraitDeleteItem(bpy.types.Operator):
# Delete the selected item from the list
bl_idname = "my_traitlist.delete_item"
bl_label = "Deletes an item"
@classmethod
def poll(self, context):
""" Enable if there's something in the list """
return len(bpy.context.object.my_traitlist) > 0
def execute(self, context):
list = bpy.context.object.my_traitlist
index = bpy.context.object.traitlist_index
list.remove(index)
if index > 0:
index = index - 1
bpy.context.object.traitlist_index = index
return{'FINISHED'}
class LIST_OT_TraitMoveItem(bpy.types.Operator):
# Move an item in the list
bl_idname = "my_traitlist.move_item"
bl_label = "Move an item in the list"
direction = bpy.props.EnumProperty(
items=(
('UP', 'Up', ""),
('DOWN', 'Down', ""),))
@classmethod
def poll(self, context):
""" Enable if there's something in the list. """
return len(bpy.context.object.my_traitlist) > 0
def move_index(self):
# Move index of an item render queue while clamping it
index = bpy.context.object.traitlist_index
list_length = len(bpy.context.object.my_traitlist) - 1
new_index = 0
if self.direction == 'UP':
new_index = index - 1
elif self.direction == 'DOWN':
new_index = index + 1
new_index = max(0, min(new_index, list_length))
index = new_index
def execute(self, context):
list = bpy.context.object.my_traitlist
index = bpy.context.object.traitlist_index
if self.direction == 'DOWN':
neighbor = index + 1
#queue.move(index,neighbor)
self.move_index()
elif self.direction == 'UP':
neighbor = index - 1
#queue.move(neighbor, index)
self.move_index()
else:
return{'CANCELLED'}
return{'FINISHED'}
# Menu in tools region
class ToolsTraitsPanel(bpy.types.Panel):
bl_label = "zblend_traits"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
def draw(self, context):
layout = self.layout
obj = bpy.context.object
rows = 2
if len(obj.my_traitlist) > 1:
rows = 4
row = layout.row()
row.template_list("MY_UL_TraitList", "The_List", obj, "my_traitlist", obj, "traitlist_index", rows=rows)
col = row.column(align=True)
col.operator("my_traitlist.new_item", icon='ZOOMIN', text="")
col.operator("my_traitlist.delete_item", icon='ZOOMOUT', text="")#.all = False
if len(obj.my_traitlist) > 1:
col.separator()
col.operator("my_traitlist.move_item", icon='TRIA_UP', text="").direction = 'UP'
col.operator("my_traitlist.move_item", icon='TRIA_DOWN', text="").direction = 'DOWN'
if obj.traitlist_index >= 0 and len(obj.my_traitlist) > 0:
item = obj.my_traitlist[obj.traitlist_index]
# Default props
row = layout.row()
row.prop(item, "type_prop")
# Script
if item.type_prop =='Script':
item.name = item.class_name_prop
row = layout.row()
row.prop(item, "class_name_prop")
# Nodes
elif item.type_prop =='Nodes':
item.name = item.nodes_name_prop
row = layout.row()
row.prop_search(item, "nodes_name_prop", bpy.data, "node_groups", "Tree")
# Camera
elif item.type_prop == 'Camera':
item.name = item.type_prop
row = layout.row()
row.prop_search(item, "camera_link_prop", bpy.data, "cameras", "Camera")
# Light
elif item.type_prop == 'Light':
item.name = item.type_prop
row = layout.row()
row.prop_search(item, "light_link_prop", bpy.data, "lamps", "Light")
# Rigid body
elif item.type_prop == 'Rigid Body':
item.name = item.type_prop
row = layout.row()
row.prop(item, "default_body_prop")
if item.default_body_prop == False:
row.prop_search(item, "body_link_prop", bpy.context.scene.rigidbody_world.group, "objects", "")
row = layout.row()
row.prop(item, "custom_shape_prop")
if item.custom_shape_prop == True:
row.prop(item, "custom_shape_type_prop")
row = layout.row()
row.prop(item, "shape_size_scale_prop")
# Scene instance
elif item.type_prop == 'Scene Instance':
item.name = item.type_prop
row = layout.row()
row.prop_search(item, "scene_prop", bpy.data, "scenes", "Scene")
#row.prop(item, "scene_prop")
# Animation
elif item.type_prop == 'Animation':
item.name = item.type_prop
row = layout.row()
row.prop_search(item, "start_track_name_prop", obj, "my_animationtraitlist", "Start Track")
# List
animrow = layout.row()
animrows = 2
if len(obj.my_animationtraitlist) > 1:
animrows = 4
row = layout.row()
row.template_list("MY_UL_AnimationTraitList", "The_List", obj, "my_animationtraitlist", obj, "animationtraitlist_index", rows=animrows)
col = row.column(align=True)
col.operator("my_animationtraitlist.new_item", icon='ZOOMIN', text="")
col.operator("my_animationtraitlist.delete_item", icon='ZOOMOUT', text="")
if len(obj.my_animationtraitlist) > 1:
col.separator()
col.operator("my_animationtraitlist.move_item", icon='TRIA_UP', text="").direction = 'UP'
col.operator("my_animationtraitlist.move_item", icon='TRIA_DOWN', text="").direction = 'DOWN'
if obj.animationtraitlist_index >= 0 and len(obj.my_animationtraitlist) > 0:
item = obj.my_animationtraitlist[obj.animationtraitlist_index]
row = layout.row()
row.prop(item, "start_prop")
row.prop(item, "end_prop")
# Animation end
# Mesh renderer
elif item.type_prop == 'Mesh Renderer':
item.name = item.type_prop
row = layout.row()
row.prop(item, "default_material_prop")
if item.default_material_prop == False:
row.prop_search(item, "material_prop", bpy.data, "materials", "")
row = layout.row()
row.prop(item, "lighting_prop")
row = layout.row()
row.prop(item, "cast_shadow_prop")
row = layout.row()
row.prop(item, "receive_shadow_prop")
# Custom renderer
elif item.type_prop == 'Custom Renderer':
item.name = item.class_name_prop
row = layout.row()
row.prop(item, "class_name_prop")
row = layout.row()
row.prop(item, "default_material_prop")
if item.default_material_prop == False:
row.prop_search(item, "material_prop", bpy.data, "materials", "")
row = layout.row()
row.prop(item, "shader_prop")
row = layout.row()
row.prop(item, "data_prop")
# Billboard renderer
elif item.type_prop == 'Billboard Renderer':
item.name = item.type_prop
row = layout.row()
row.prop(item, "default_material_prop")
if item.default_material_prop == False:
row.prop_search(item, "material_prop", bpy.data, "materials", "")
# Particles renderer
elif item.type_prop == 'Particles Renderer':
item.name = item.type_prop
row = layout.row()
row.prop(item, "default_material_prop")
if item.default_material_prop == False:
row.prop_search(item, "material_prop", bpy.data, "materials", "")
row = layout.row()
row.prop(item, "data_prop")
# Skydome renderer
elif item.type_prop == 'Skydome Renderer':
item.name = item.type_prop
row = layout.row()
row.prop(item, "default_material_prop")
if item.default_material_prop == False:
row.prop_search(item, "material_prop", bpy.data, "materials", "")
# Registration
bpy.utils.register_module(__name__)

136
blender/traits_animation.py Executable file
View File

@ -0,0 +1,136 @@
import shutil
import bpy
import os
import json
from bpy.types import Menu, Panel, UIList
from bpy.props import *
class ListAnimationTraitItem(bpy.types.PropertyGroup):
# Group of properties representing an item in the list
name = bpy.props.StringProperty(
name="Name",
description="A name for this item",
default="Untitled")
enabled_prop = bpy.props.BoolProperty(
name="",
description="A name for this item",
default=True)
start_prop = bpy.props.IntProperty(
name="Start",
description="A name for this item",
default=0)
end_prop = bpy.props.IntProperty(
name="End",
description="A name for this item",
default=0)
bpy.utils.register_class(ListAnimationTraitItem)
class MY_UL_AnimationTraitList(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
# We could write some code to decide which icon to use here...
custom_icon = 'OBJECT_DATAMODE'
# Make sure your code supports all 3 layout types
if self.layout_type in {'DEFAULT', 'COMPACT'}:
layout.prop(item, "enabled_prop")
#layout.label(item.name, icon = custom_icon)
layout.prop(item, "name", text="", emboss=False, icon=custom_icon)
elif self.layout_type in {'GRID'}:
layout.alignment = 'CENTER'
layout.label("", icon = custom_icon)
bpy.utils.register_class(MY_UL_AnimationTraitList)
def initObjectProperties():
bpy.types.Object.my_animationtraitlist = bpy.props.CollectionProperty(type = ListAnimationTraitItem)
bpy.types.Object.animationtraitlist_index = bpy.props.IntProperty(name = "Index for my_list", default = 0)
initObjectProperties()
class LIST_OT_AnimationTraitNewItem(bpy.types.Operator):
# Add a new item to the list
bl_idname = "my_animationtraitlist.new_item"
bl_label = "Add a new item"
def execute(self, context):
bpy.context.object.my_animationtraitlist.add()
bpy.context.object.animationtraitlist_index += 1
return{'FINISHED'}
class LIST_OT_AnimationTraitDeleteItem(bpy.types.Operator):
# Delete the selected item from the list
bl_idname = "my_animationtraitlist.delete_item"
bl_label = "Deletes an item"
@classmethod
def poll(self, context):
""" Enable if there's something in the list """
return len(bpy.context.object.my_animationtraitlist) > 0
def execute(self, context):
list = bpy.context.object.my_animationtraitlist
index = bpy.context.object.animationtraitlist_index
list.remove(index)
if index > 0:
index = index - 1
bpy.context.object.animationtraitlist_index = index
return{'FINISHED'}
class LIST_OT_AnimationTraitMoveItem(bpy.types.Operator):
# Move an item in the list
bl_idname = "my_animationtraitlist.move_item"
bl_label = "Move an item in the list"
direction = bpy.props.EnumProperty(
items=(
('UP', 'Up', ""),
('DOWN', 'Down', ""),))
@classmethod
def poll(self, context):
""" Enable if there's something in the list. """
return len(bpy.context.object.my_animationtraitlist) > 0
def move_index(self):
# Move index of an item render queue while clamping it
index = bpy.context.object.animationtraitlist_index
list_length = len(bpy.context.object.my_animationtraitlist) - 1
new_index = 0
if self.direction == 'UP':
new_index = index - 1
elif self.direction == 'DOWN':
new_index = index + 1
new_index = max(0, min(new_index, list_length))
index = new_index
def execute(self, context):
list = bpy.context.object.my_animationtraitlist
index = bpy.context.object.animationtraitlist_index
if self.direction == 'DOWN':
neighbor = index + 1
#queue.move(index,neighbor)
self.move_index()
elif self.direction == 'UP':
neighbor = index - 1
#queue.move(neighbor, index)
self.move_index()
else:
return{'CANCELLED'}
return{'FINISHED'}
# Registration
bpy.utils.register_module(__name__)