4304be7a0a
Add buttons: - New Node Tree – creates and sets a new Node Tree in the list; - Edit Node Tree – loads the Node Tree selected from the list into all Logic Node Editor; - From Node Tree – loops through all the working Logic Node Editor and from the last in the list sets the value to the list.
945 lines
36 KiB
Python
Executable file
945 lines
36 KiB
Python
Executable file
import json
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import webbrowser
|
|
|
|
from bpy.types import NodeTree
|
|
import bpy.utils.previews
|
|
|
|
import arm.make as make
|
|
from arm.props_traits_props import *
|
|
import arm.proxy as proxy
|
|
import arm.utils
|
|
import arm.write_data as write_data
|
|
|
|
|
|
def trigger_recompile(self, context):
|
|
wrd = bpy.data.worlds['Arm']
|
|
wrd.arm_recompile = True
|
|
|
|
def update_trait_group(self, context):
|
|
o = context.object if self.is_object else context.scene
|
|
if o == None:
|
|
return
|
|
i = o.arm_traitlist_index
|
|
if i >= 0 and i < len(o.arm_traitlist):
|
|
t = o.arm_traitlist[i]
|
|
if t.type_prop == 'Haxe Script' or t.type_prop == 'Bundled Script':
|
|
t.name = t.class_name_prop
|
|
elif t.type_prop == 'WebAssembly':
|
|
t.name = t.webassembly_prop
|
|
elif t.type_prop == 'UI Canvas':
|
|
t.name = t.canvas_name_prop
|
|
elif t.type_prop == 'Logic Nodes':
|
|
if t.node_tree_prop != None:
|
|
t.name = t.node_tree_prop.name
|
|
# Fetch props
|
|
if t.type_prop == 'Bundled Script' and t.name != '':
|
|
file_path = arm.utils.get_sdk_path() + '/armory/Sources/armory/trait/' + t.name + '.hx'
|
|
if os.path.exists(file_path):
|
|
arm.utils.fetch_script_props(file_path)
|
|
arm.utils.fetch_prop(o)
|
|
# Show trait users as collections
|
|
if self.is_object:
|
|
for col in bpy.data.collections:
|
|
if col.name.startswith('Trait|') and o.name in col.objects:
|
|
col.objects.unlink(o)
|
|
for t in o.arm_traitlist:
|
|
if 'Trait|' + t.name not in bpy.data.collections:
|
|
col = bpy.data.collections.new('Trait|' + t.name)
|
|
else:
|
|
col = bpy.data.collections['Trait|' + t.name]
|
|
try:
|
|
col.objects.link(o)
|
|
except RuntimeError:
|
|
# Object is already in that collection. This can
|
|
# happen when multiple same traits are copied with
|
|
# bpy.ops.arm.copy_traits_to_active
|
|
pass
|
|
|
|
class ArmTraitListItem(bpy.types.PropertyGroup):
|
|
name: StringProperty(name="Name", description="A name for this item", default="")
|
|
enabled_prop: BoolProperty(name="", description="A name for this item", default=True, update=trigger_recompile)
|
|
is_object: BoolProperty(name="", default=True)
|
|
fake_user: BoolProperty(name="Fake User", description="Export this trait even if it is deactivated", default=False)
|
|
type_prop: EnumProperty(
|
|
items = [('Haxe Script', 'Haxe', 'Haxe Script'),
|
|
('WebAssembly', 'Wasm', 'WebAssembly'),
|
|
('UI Canvas', 'UI', 'UI Canvas'),
|
|
('Bundled Script', 'Bundled', 'Bundled Script'),
|
|
('Logic Nodes', 'Nodes', 'Logic Nodes')
|
|
],
|
|
name = "Type")
|
|
class_name_prop: StringProperty(name="Class", description="A name for this item", default="", update=update_trait_group)
|
|
canvas_name_prop: StringProperty(name="Canvas", description="A name for this item", default="", update=update_trait_group)
|
|
webassembly_prop: StringProperty(name="Module", description="A name for this item", default="", update=update_trait_group)
|
|
node_tree_prop: PointerProperty(type=NodeTree, update=update_trait_group)
|
|
arm_traitpropslist: CollectionProperty(type=ArmTraitPropListItem)
|
|
arm_traitpropslist_index: IntProperty(name="Index for my_list", default=0)
|
|
arm_traitpropswarnings: CollectionProperty(type=ArmTraitPropWarning)
|
|
|
|
class ARM_UL_TraitList(bpy.types.UIList):
|
|
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
|
custom_icon = "NONE"
|
|
custom_icon_value = 0
|
|
if item.type_prop == "Haxe Script":
|
|
custom_icon_value = icons_dict["haxe"].icon_id
|
|
elif item.type_prop == "WebAssembly":
|
|
custom_icon_value = icons_dict["wasm"].icon_id
|
|
elif item.type_prop == "UI Canvas":
|
|
custom_icon = "OBJECT_DATAMODE"
|
|
elif item.type_prop == "Bundled Script":
|
|
custom_icon = 'OBJECT_DATAMODE'
|
|
elif item.type_prop == "Logic Nodes":
|
|
custom_icon = 'NODETREE'
|
|
|
|
# Make sure your code supports all 3 layout types
|
|
if self.layout_type in {'DEFAULT', 'COMPACT'}:
|
|
layout.prop(item, "enabled_prop")
|
|
# Display " " for props without a name to right-align the
|
|
# fake_user button
|
|
layout.label(text=item.name if item.name != "" else " ", icon=custom_icon, icon_value=custom_icon_value)
|
|
|
|
elif self.layout_type in {'GRID'}:
|
|
layout.alignment = 'CENTER'
|
|
layout.label(text="", icon=custom_icon, icon_value=custom_icon_value)
|
|
|
|
layout.prop(item, "fake_user", text="", icon="FAKE_USER_ON" if item.fake_user else "FAKE_USER_OFF")
|
|
|
|
class ArmTraitListNewItem(bpy.types.Operator):
|
|
bl_idname = "arm_traitlist.new_item"
|
|
bl_label = "New Trait Item"
|
|
bl_description = "Add a new trait item to the list"
|
|
|
|
is_object: BoolProperty(name="Object Trait", description="Whether this is an object or scene trait", default=False)
|
|
type_prop: EnumProperty(
|
|
items = [('Haxe Script', 'Haxe', 'Haxe Script'),
|
|
('WebAssembly', 'Wasm', 'WebAssembly'),
|
|
('UI Canvas', 'UI', 'UI Canvas'),
|
|
('Bundled Script', 'Bundled', 'Bundled Script'),
|
|
('Logic Nodes', 'Nodes', 'Logic Nodes')
|
|
],
|
|
name = "Type")
|
|
|
|
def invoke(self, context, event):
|
|
wm = context.window_manager
|
|
return wm.invoke_props_dialog(self)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
# Todo: show is_object property when called from operator search menu
|
|
# layout.prop(self, "is_object")
|
|
layout.prop(self, "type_prop", expand=True)
|
|
|
|
def execute(self, context):
|
|
if self.is_object:
|
|
obj = bpy.context.object
|
|
else:
|
|
obj = bpy.context.scene
|
|
trait = obj.arm_traitlist.add()
|
|
trait.is_object = self.is_object
|
|
trait.type_prop = self.type_prop
|
|
obj.arm_traitlist_index = len(obj.arm_traitlist) - 1
|
|
trigger_recompile(None, None)
|
|
return{'FINISHED'}
|
|
|
|
class ArmTraitListDeleteItem(bpy.types.Operator):
|
|
"""Delete the selected item from the list"""
|
|
bl_idname = "arm_traitlist.delete_item"
|
|
bl_label = "Deletes an item"
|
|
bl_options = {'INTERNAL'}
|
|
|
|
is_object: BoolProperty(name="", description="A name for this item", default=False)
|
|
|
|
@classmethod
|
|
def poll(self, context):
|
|
""" Enable if there's something in the list """
|
|
obj = bpy.context.object
|
|
if obj == None:
|
|
return False
|
|
return len(obj.arm_traitlist) > 0
|
|
|
|
def execute(self, context):
|
|
obj = bpy.context.object
|
|
lst = obj.arm_traitlist
|
|
index = obj.arm_traitlist_index
|
|
|
|
if len(lst) <= index:
|
|
return{'FINISHED'}
|
|
|
|
lst.remove(index)
|
|
update_trait_group(self, context)
|
|
|
|
if index > 0:
|
|
index = index - 1
|
|
|
|
obj.arm_traitlist_index = index
|
|
return{'FINISHED'}
|
|
|
|
class ArmTraitListDeleteItemScene(bpy.types.Operator):
|
|
"""Delete the selected item from the list"""
|
|
bl_idname = "arm_traitlist.delete_item_scene"
|
|
bl_label = "Deletes an item"
|
|
bl_options = {'INTERNAL'}
|
|
|
|
is_object: BoolProperty(name="", description="A name for this item", default=False)
|
|
|
|
@classmethod
|
|
def poll(self, context):
|
|
""" Enable if there's something in the list """
|
|
obj = bpy.context.scene
|
|
if obj == None:
|
|
return False
|
|
return len(obj.arm_traitlist) > 0
|
|
|
|
def execute(self, context):
|
|
obj = bpy.context.scene
|
|
lst = obj.arm_traitlist
|
|
index = obj.arm_traitlist_index
|
|
|
|
if len(lst) <= index:
|
|
return{'FINISHED'}
|
|
|
|
lst.remove(index)
|
|
|
|
if index > 0:
|
|
index = index - 1
|
|
|
|
obj.arm_traitlist_index = index
|
|
return{'FINISHED'}
|
|
|
|
class ArmTraitListMoveItem(bpy.types.Operator):
|
|
"""Move an item in the list"""
|
|
bl_idname = "arm_traitlist.move_item"
|
|
bl_label = "Move an item in the list"
|
|
bl_options = {'INTERNAL'}
|
|
|
|
direction: EnumProperty(
|
|
items=(
|
|
('UP', 'Up', ""),
|
|
('DOWN', 'Down', ""),))
|
|
|
|
is_object: BoolProperty(name="", description="A name for this item", default=False)
|
|
|
|
def move_index(self):
|
|
# Move index of an item render queue while clamping it
|
|
if self.is_object:
|
|
obj = bpy.context.object
|
|
else:
|
|
obj = bpy.context.scene
|
|
index = obj.arm_traitlist_index
|
|
list_length = len(obj.arm_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))
|
|
obj.arm_traitlist.move(index, new_index)
|
|
obj.arm_traitlist_index = new_index
|
|
|
|
def execute(self, context):
|
|
if self.is_object:
|
|
obj = bpy.context.object
|
|
else:
|
|
obj = bpy.context.scene
|
|
list = obj.arm_traitlist
|
|
index = obj.arm_traitlist_index
|
|
|
|
if self.direction == 'DOWN':
|
|
neighbor = index + 1
|
|
self.move_index()
|
|
|
|
elif self.direction == 'UP':
|
|
neighbor = index - 1
|
|
self.move_index()
|
|
else:
|
|
return{'CANCELLED'}
|
|
return{'FINISHED'}
|
|
|
|
class ArmEditScriptButton(bpy.types.Operator):
|
|
bl_idname = 'arm.edit_script'
|
|
bl_label = 'Edit Script'
|
|
bl_description = 'Edit script in the text editor'
|
|
bl_options = {'INTERNAL'}
|
|
|
|
is_object: BoolProperty(name="", description="A name for this item", default=False)
|
|
|
|
def execute(self, context):
|
|
|
|
arm.utils.check_default_props()
|
|
|
|
if not os.path.exists(arm.utils.get_fp() + "/khafile.js"):
|
|
print('Generating Krom project for IDE build configuration')
|
|
make.build('krom')
|
|
|
|
if self.is_object:
|
|
obj = bpy.context.object
|
|
else:
|
|
obj = bpy.context.scene
|
|
|
|
item = obj.arm_traitlist[obj.arm_traitlist_index]
|
|
pkg = arm.utils.safestr(bpy.data.worlds['Arm'].arm_project_package)
|
|
# Replace the haxe package syntax with the os-dependent path syntax for opening
|
|
hx_path = arm.utils.get_fp() + '/Sources/' + pkg + '/' + item.class_name_prop.replace('.', os.sep) + '.hx'
|
|
arm.utils.open_editor(hx_path)
|
|
return{'FINISHED'}
|
|
|
|
class ArmEditBundledScriptButton(bpy.types.Operator):
|
|
bl_idname = 'arm.edit_bundled_script'
|
|
bl_label = 'Edit Script'
|
|
bl_description = 'Copy script to project and edit in the text editor'
|
|
bl_options = {'INTERNAL'}
|
|
|
|
is_object: BoolProperty(name="", description="A name for this item", default=False)
|
|
|
|
def execute(self, context):
|
|
if not arm.utils.check_saved(self):
|
|
return {'CANCELLED'}
|
|
|
|
if self.is_object:
|
|
obj = bpy.context.object
|
|
else:
|
|
obj = bpy.context.scene
|
|
sdk_path = arm.utils.get_sdk_path()
|
|
project_path = arm.utils.get_fp()
|
|
pkg = arm.utils.safestr(bpy.data.worlds['Arm'].arm_project_package)
|
|
item = obj.arm_traitlist[obj.arm_traitlist_index]
|
|
source_hx_path = sdk_path + '/armory/Sources/armory/trait/' + item.class_name_prop + '.hx'
|
|
target_hx_path = project_path + '/Sources/' + pkg + '/' + item.class_name_prop + '.hx'
|
|
|
|
if not os.path.isfile(target_hx_path):
|
|
# Rewrite package and copy
|
|
sf = open(source_hx_path)
|
|
sf.readline()
|
|
tf = open(target_hx_path, 'w')
|
|
tf.write('package ' + pkg + ';\n')
|
|
shutil.copyfileobj(sf, tf)
|
|
sf.close()
|
|
tf.close()
|
|
arm.utils.fetch_script_names()
|
|
|
|
# From bundled to script
|
|
item.type_prop = 'Haxe Script'
|
|
|
|
# Edit in Kode Studio
|
|
bpy.ops.arm.edit_script('EXEC_DEFAULT', is_object=self.is_object)
|
|
|
|
return{'FINISHED'}
|
|
|
|
class ArmoryGenerateNavmeshButton(bpy.types.Operator):
|
|
"""Generate navmesh from selected meshes"""
|
|
bl_idname = 'arm.generate_navmesh'
|
|
bl_label = 'Generate Navmesh'
|
|
|
|
def execute(self, context):
|
|
obj = context.active_object
|
|
|
|
if obj.type != 'MESH':
|
|
return{'CANCELLED'}
|
|
|
|
if not arm.utils.check_saved(self):
|
|
return {"CANCELLED"}
|
|
|
|
if not arm.utils.check_sdkpath(self):
|
|
return {"CANCELLED"}
|
|
|
|
depsgraph = bpy.context.evaluated_depsgraph_get()
|
|
armature = obj.find_armature()
|
|
apply_modifiers = not armature
|
|
|
|
obj_eval = obj.evaluated_get(depsgraph) if apply_modifiers else obj
|
|
export_mesh = obj_eval.to_mesh()
|
|
# TODO: build tilecache here
|
|
print("Started visualization generation")
|
|
# For visualization
|
|
nav_full_path = arm.utils.get_fp_build() + '/compiled/Assets/navigation'
|
|
if not os.path.exists(nav_full_path):
|
|
os.makedirs(nav_full_path)
|
|
|
|
nav_mesh_name = 'nav_' + obj_eval.data.name
|
|
mesh_path = nav_full_path + '/' + nav_mesh_name + '.obj'
|
|
|
|
with open(mesh_path, 'w') as f:
|
|
for v in export_mesh.vertices:
|
|
f.write("v %.4f " % (v.co[0] * obj_eval.scale.x))
|
|
f.write("%.4f " % (v.co[2] * obj_eval.scale.z))
|
|
f.write("%.4f\n" % (v.co[1] * obj_eval.scale.y)) # Flipped
|
|
for p in export_mesh.polygons:
|
|
f.write("f")
|
|
for i in reversed(p.vertices): # Flipped normals
|
|
f.write(" %d" % (i + 1))
|
|
f.write("\n")
|
|
|
|
buildnavjs_path = arm.utils.get_sdk_path() + '/lib/haxerecast/buildnavjs'
|
|
|
|
# append config values
|
|
nav_config = {}
|
|
for trait in obj.arm_traitlist:
|
|
# check if trait is navmesh here
|
|
if trait.arm_traitpropslist and trait.class_name_prop == 'NavMesh':
|
|
for prop in trait.arm_traitpropslist: # Append props
|
|
name = prop.name
|
|
value = prop.get_value()
|
|
nav_config[name] = value
|
|
nav_config_json = json.dumps(nav_config)
|
|
|
|
args = [arm.utils.get_node_path(), buildnavjs_path, nav_mesh_name, nav_config_json]
|
|
proc = subprocess.Popen(args, cwd=nav_full_path)
|
|
proc.wait()
|
|
|
|
navmesh = bpy.ops.import_scene.obj(filepath=mesh_path)
|
|
navmesh = bpy.context.selected_objects[0]
|
|
|
|
navmesh.name = nav_mesh_name
|
|
navmesh.rotation_euler = (0, 0, 0)
|
|
navmesh.location = (obj.location.x, obj.location.y, obj.location.z)
|
|
navmesh.arm_export = False
|
|
|
|
bpy.context.view_layer.objects.active = navmesh
|
|
bpy.ops.object.editmode_toggle()
|
|
bpy.ops.mesh.select_all(action='SELECT')
|
|
bpy.ops.mesh.remove_doubles()
|
|
bpy.ops.object.editmode_toggle()
|
|
|
|
obj_eval.to_mesh_clear()
|
|
|
|
print("Finished visualization generation")
|
|
|
|
return{'FINISHED'}
|
|
|
|
class ArmEditCanvasButton(bpy.types.Operator):
|
|
bl_idname = 'arm.edit_canvas'
|
|
bl_label = 'Edit Canvas'
|
|
bl_description = 'Edit UI Canvas'
|
|
bl_options = {'INTERNAL'}
|
|
|
|
is_object: BoolProperty(name="", description="A name for this item", default=False)
|
|
|
|
def execute(self, context):
|
|
if self.is_object:
|
|
obj = bpy.context.object
|
|
else:
|
|
obj = bpy.context.scene
|
|
project_path = arm.utils.get_fp()
|
|
item = obj.arm_traitlist[obj.arm_traitlist_index]
|
|
canvas_path = project_path + '/Bundled/canvas/' + item.canvas_name_prop + '.json'
|
|
sdk_path = arm.utils.get_sdk_path()
|
|
ext = 'd3d11' if arm.utils.get_os() == 'win' else 'opengl'
|
|
armory2d_path = sdk_path + '/lib/armory_tools/armory2d/' + ext
|
|
krom_location, krom_path = arm.utils.krom_paths()
|
|
os.chdir(krom_location)
|
|
cpath = canvas_path.replace('\\', '/')
|
|
uiscale = str(arm.utils.get_ui_scale())
|
|
cmd = [krom_path, armory2d_path, armory2d_path, cpath, uiscale]
|
|
if arm.utils.get_os() == 'win':
|
|
cmd.append('--consolepid')
|
|
cmd.append(str(os.getpid()))
|
|
subprocess.Popen(cmd)
|
|
return{'FINISHED'}
|
|
|
|
class ArmNewScriptDialog(bpy.types.Operator):
|
|
bl_idname = "arm.new_script"
|
|
bl_label = "New Script"
|
|
bl_description = 'Create a blank script'
|
|
bl_options = {'INTERNAL'}
|
|
|
|
is_object: BoolProperty(name="Object trait", description="Is this an object trait?", default=False)
|
|
class_name: StringProperty(name="Name", description="The class name")
|
|
|
|
def execute(self, context):
|
|
if self.is_object:
|
|
obj = bpy.context.object
|
|
else:
|
|
obj = bpy.context.scene
|
|
self.class_name = self.class_name.replace(' ', '')
|
|
write_data.write_traithx(self.class_name)
|
|
arm.utils.fetch_script_names()
|
|
item = obj.arm_traitlist[obj.arm_traitlist_index]
|
|
item.class_name_prop = self.class_name
|
|
return {'FINISHED'}
|
|
|
|
def invoke(self, context, event):
|
|
if not arm.utils.check_saved(self):
|
|
return {'CANCELLED'}
|
|
self.class_name = 'MyTrait'
|
|
return context.window_manager.invoke_props_dialog(self)
|
|
|
|
def draw(self, context):
|
|
self.layout.prop(self, "class_name")
|
|
|
|
class ArmNewTreeNodeDialog(bpy.types.Operator):
|
|
bl_idname = "arm.new_treenode"
|
|
bl_label = "New Node Tree"
|
|
bl_description = 'Create a blank Node Tree'
|
|
bl_options = {'INTERNAL'}
|
|
|
|
is_object: BoolProperty(name="Object Node Tree", description="Is this an object Node Tree?", default=False)
|
|
class_name: StringProperty(name="Name", description="The Node Tree name")
|
|
|
|
def execute(self, context):
|
|
if self.is_object:
|
|
obj = context.object
|
|
else:
|
|
obj = context.scene
|
|
self.class_name = self.class_name.replace(' ', '')
|
|
# Create new node tree
|
|
node_tree = bpy.data.node_groups.new(self.class_name, 'ArmLogicTreeType')
|
|
# Set new node tree
|
|
item = obj.arm_traitlist[obj.arm_traitlist_index]
|
|
if item.node_tree_prop is None:
|
|
item.node_tree_prop = node_tree
|
|
return {'FINISHED'}
|
|
|
|
def invoke(self, context, event):
|
|
if not arm.utils.check_saved(self):
|
|
return {'CANCELLED'}
|
|
self.class_name = 'MyNodeTree'
|
|
return context.window_manager.invoke_props_dialog(self)
|
|
|
|
def draw(self, context):
|
|
self.layout.prop(self, "class_name")
|
|
|
|
class ArmEditTreeNodeDialog(bpy.types.Operator):
|
|
bl_idname = "arm.edit_treenode"
|
|
bl_label = "Edit Node Tree"
|
|
bl_description = 'Edit this Node Tree in the Logic Node Editor'
|
|
bl_options = {'INTERNAL'}
|
|
|
|
is_object: BoolProperty(name="Object Node Tree", description="Is this an object Node Tree?", default=False)
|
|
|
|
def execute(self, context):
|
|
if self.is_object:
|
|
obj = context.object
|
|
else:
|
|
obj = context.scene
|
|
# Check len node tree list
|
|
if len(obj.arm_traitlist) > 0:
|
|
item = obj.arm_traitlist[obj.arm_traitlist_index]
|
|
# Loop for all spaces
|
|
context_screen = context.screen
|
|
if item is not None and context_screen is not None:
|
|
areas = context_screen.areas
|
|
for area in areas:
|
|
for space in area.spaces:
|
|
if space.type == 'NODE_EDITOR':
|
|
if space.tree_type == 'ArmLogicTreeType':
|
|
# Set Node Tree
|
|
space.node_tree = item.node_tree_prop
|
|
return {'FINISHED'}
|
|
|
|
class ArmGetTreeNodeDialog(bpy.types.Operator):
|
|
bl_idname = "arm.get_treenode"
|
|
bl_label = "From Node Editor"
|
|
bl_description = 'Use the Node Tree from the opened Node Tree Editor for this trait'
|
|
bl_options = {'INTERNAL'}
|
|
|
|
is_object: BoolProperty(name="Object Node Tree", description="Is this an object Node Tree?", default=False)
|
|
|
|
def execute(self, context):
|
|
if self.is_object:
|
|
obj = context.object
|
|
else:
|
|
obj = context.scene
|
|
# Check len node tree list
|
|
if len(obj.arm_traitlist) > 0:
|
|
item = obj.arm_traitlist[obj.arm_traitlist_index]
|
|
# Loop for all spaces
|
|
context_screen = context.screen
|
|
if item is not None and context_screen is not None:
|
|
areas = context_screen.areas
|
|
for area in areas:
|
|
for space in area.spaces:
|
|
if space.type == 'NODE_EDITOR':
|
|
if space.tree_type == 'ArmLogicTreeType' and space.node_tree is not None:
|
|
# Set Node Tree in Item
|
|
item.node_tree_prop = space.node_tree
|
|
return {'FINISHED'}
|
|
|
|
class ArmNewCanvasDialog(bpy.types.Operator):
|
|
bl_idname = "arm.new_canvas"
|
|
bl_label = "New Canvas"
|
|
bl_description = 'Create a blank canvas'
|
|
bl_options = {'INTERNAL'}
|
|
|
|
is_object: BoolProperty(name="Object trait", description="Is this an object trait?", default=False)
|
|
canvas_name: StringProperty(name="Name", description="The canvas name")
|
|
|
|
def execute(self, context):
|
|
if self.is_object:
|
|
obj = bpy.context.object
|
|
else:
|
|
obj = bpy.context.scene
|
|
self.canvas_name = self.canvas_name.replace(' ', '')
|
|
write_data.write_canvasjson(self.canvas_name)
|
|
arm.utils.fetch_script_names()
|
|
# Todo: create new trait item when called from operator search
|
|
# menu, then remove 'INTERNAL' from bl_options
|
|
item = obj.arm_traitlist[obj.arm_traitlist_index]
|
|
item.canvas_name_prop = self.canvas_name
|
|
return {'FINISHED'}
|
|
|
|
def invoke(self, context, event):
|
|
if not arm.utils.check_saved(self):
|
|
return {'CANCELLED'}
|
|
self.canvas_name = 'MyCanvas'
|
|
return context.window_manager.invoke_props_dialog(self)
|
|
|
|
def draw(self, context):
|
|
self.layout.prop(self, "canvas_name")
|
|
|
|
class ArmNewWasmButton(bpy.types.Operator):
|
|
"""Create new WebAssembly module"""
|
|
bl_idname = 'arm.new_wasm'
|
|
bl_label = 'New Module'
|
|
|
|
def execute(self, context):
|
|
webbrowser.open('https://webassembly.studio/')
|
|
return{'FINISHED'}
|
|
|
|
class ArmRefreshScriptsButton(bpy.types.Operator):
|
|
"""Fetch all script names"""
|
|
bl_idname = 'arm.refresh_scripts'
|
|
bl_label = 'Refresh Traits'
|
|
|
|
def execute(self, context):
|
|
arm.utils.fetch_bundled_script_names()
|
|
arm.utils.fetch_bundled_trait_props()
|
|
arm.utils.fetch_script_names()
|
|
arm.utils.fetch_trait_props()
|
|
arm.utils.fetch_wasm_names()
|
|
return{'FINISHED'}
|
|
|
|
class ArmRefreshCanvasListButton(bpy.types.Operator):
|
|
"""Fetch all canvas names"""
|
|
bl_idname = 'arm.refresh_canvas_list'
|
|
bl_label = 'Refresh Canvas Traits'
|
|
|
|
def execute(self, context):
|
|
arm.utils.fetch_script_names()
|
|
return{'FINISHED'}
|
|
|
|
class ARM_PT_TraitPanel(bpy.types.Panel):
|
|
bl_label = "Armory Traits"
|
|
bl_space_type = "PROPERTIES"
|
|
bl_region_type = "WINDOW"
|
|
bl_context = "object"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
layout.use_property_decorate = False
|
|
obj = bpy.context.object
|
|
draw_traits(layout, obj, is_object=True)
|
|
|
|
class ARM_PT_SceneTraitPanel(bpy.types.Panel):
|
|
bl_label = "Armory Scene Traits"
|
|
bl_space_type = "PROPERTIES"
|
|
bl_region_type = "WINDOW"
|
|
bl_context = "scene"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
layout.use_property_decorate = False
|
|
obj = bpy.context.scene
|
|
draw_traits(layout, obj, is_object=False)
|
|
|
|
class ARM_OT_CopyTraitsFromActive(bpy.types.Operator):
|
|
bl_label = 'Copy Traits from Active Object'
|
|
bl_idname = 'arm.copy_traits_to_active'
|
|
bl_description = 'Copies the traits of the active object to all other selected objects'
|
|
|
|
overwrite: BoolProperty(name="Overwrite", default=True)
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.active_object is not None and len(context.selected_objects) > 1
|
|
|
|
def draw_message_box(self, context):
|
|
layout = self.layout
|
|
layout = layout.column(align=True)
|
|
layout.alignment = 'EXPAND'
|
|
|
|
layout.label(text='Warning: At least one target object already has', icon='ERROR')
|
|
layout.label(text='traits assigned to it!', icon='BLANK1')
|
|
layout.separator()
|
|
layout.label(text='Do you want to overwrite the already existing traits', icon='BLANK1')
|
|
layout.label(text='or append to them?', icon='BLANK1')
|
|
layout.separator()
|
|
|
|
row = layout.row(align=True)
|
|
row.active_default = True
|
|
row.operator('arm.copy_traits_to_active', text='Overwrite').overwrite = True
|
|
row.active_default = False
|
|
row.operator('arm.copy_traits_to_active', text='Append').overwrite = False
|
|
row.operator('arm.discard_popup', text='Cancel')
|
|
|
|
def execute(self, context):
|
|
source_obj = bpy.context.active_object
|
|
|
|
for target_obj in bpy.context.selected_objects:
|
|
if source_obj == target_obj:
|
|
continue
|
|
|
|
# Offset for trait iteration when appending traits
|
|
offset = 0
|
|
if not self.overwrite:
|
|
offset = len(target_obj.arm_traitlist)
|
|
|
|
# Make use of proxy functions here
|
|
proxy.sync_collection(
|
|
source_obj.arm_traitlist, target_obj.arm_traitlist, clear_dst=self.overwrite)
|
|
|
|
for i in range(len(source_obj.arm_traitlist)):
|
|
proxy.sync_collection(
|
|
source_obj.arm_traitlist[i].arm_traitpropslist,
|
|
target_obj.arm_traitlist[i + offset].arm_traitpropslist
|
|
)
|
|
|
|
return {"FINISHED"}
|
|
|
|
def invoke(self, context, event):
|
|
show_dialog = False
|
|
|
|
# Test if there is a target object which has traits that would
|
|
# get overwritten
|
|
source_obj = bpy.context.active_object
|
|
for target_object in bpy.context.selected_objects:
|
|
if source_obj == target_object:
|
|
continue
|
|
else:
|
|
if target_object.arm_traitlist:
|
|
show_dialog = True
|
|
|
|
if show_dialog:
|
|
context.window_manager.popover(self.__class__.draw_message_box, ui_units_x=16)
|
|
else:
|
|
bpy.ops.arm.copy_traits_to_active()
|
|
|
|
return {'INTERFACE'}
|
|
|
|
def draw_traits(layout, obj, is_object):
|
|
rows = 2
|
|
if len(obj.arm_traitlist) > 1:
|
|
rows = 4
|
|
|
|
row = layout.row()
|
|
row.template_list("ARM_UL_TraitList", "The_List", obj, "arm_traitlist", obj, "arm_traitlist_index", rows=rows)
|
|
|
|
col = row.column(align=True)
|
|
op = col.operator("arm_traitlist.new_item", icon='ADD', text="")
|
|
op.is_object = is_object
|
|
if is_object:
|
|
op = col.operator("arm_traitlist.delete_item", icon='REMOVE', text="")#.all = False
|
|
else:
|
|
op = col.operator("arm_traitlist.delete_item_scene", icon='REMOVE', text="")#.all = False
|
|
op.is_object = is_object
|
|
|
|
if len(obj.arm_traitlist) > 1:
|
|
col.separator()
|
|
op = col.operator("arm_traitlist.move_item", icon='TRIA_UP', text="")
|
|
op.direction = 'UP'
|
|
op.is_object = is_object
|
|
op = col.operator("arm_traitlist.move_item", icon='TRIA_DOWN', text="")
|
|
op.direction = 'DOWN'
|
|
op.is_object = is_object
|
|
|
|
if obj.arm_traitlist_index >= 0 and len(obj.arm_traitlist) > 0:
|
|
item = obj.arm_traitlist[obj.arm_traitlist_index]
|
|
|
|
if item.type_prop == 'Haxe Script' or item.type_prop == 'Bundled Script':
|
|
if item.type_prop == 'Haxe Script':
|
|
row = layout.row(align=True)
|
|
row.alignment = 'EXPAND'
|
|
column = row.column(align=True)
|
|
column.alignment = 'EXPAND'
|
|
if item.class_name_prop == '':
|
|
column.enabled = False
|
|
op = column.operator("arm.edit_script", icon="FILE_SCRIPT")
|
|
op.is_object = is_object
|
|
op = row.operator("arm.new_script")
|
|
op.is_object = is_object
|
|
op = row.operator("arm.refresh_scripts", text="Refresh")
|
|
else: # Bundled
|
|
if item.class_name_prop == 'NavMesh':
|
|
row = layout.row(align=True)
|
|
row.alignment = 'EXPAND'
|
|
op = layout.operator("arm.generate_navmesh")
|
|
row = layout.row(align=True)
|
|
row.alignment = 'EXPAND'
|
|
column = row.column(align=True)
|
|
column.alignment = 'EXPAND'
|
|
if not item.class_name_prop == 'NavMesh':
|
|
op = column.operator("arm.edit_bundled_script", icon="FILE_SCRIPT")
|
|
op.is_object = is_object
|
|
op = row.operator("arm.refresh_scripts", text="Refresh")
|
|
|
|
# Default props
|
|
item.name = item.class_name_prop
|
|
row = layout.row()
|
|
if item.type_prop == 'Haxe Script':
|
|
row.prop_search(item, "class_name_prop", bpy.data.worlds['Arm'], "arm_scripts_list", text="Class")
|
|
else:
|
|
# Bundled scripts not yet fetched
|
|
if not bpy.data.worlds['Arm'].arm_bundled_scripts_list:
|
|
arm.utils.fetch_bundled_script_names()
|
|
row.prop_search(item, "class_name_prop", bpy.data.worlds['Arm'], "arm_bundled_scripts_list", text="Class")
|
|
|
|
elif item.type_prop == 'WebAssembly':
|
|
item.name = item.webassembly_prop
|
|
row = layout.row()
|
|
row.prop_search(item, "webassembly_prop", bpy.data.worlds['Arm'], "arm_wasm_list", text="Module")
|
|
row = layout.row(align=True)
|
|
row.alignment = 'EXPAND'
|
|
column = row.column(align=True)
|
|
column.alignment = 'EXPAND'
|
|
if item.class_name_prop == '':
|
|
column.enabled = False
|
|
# op = column.operator("arm.edit_script", icon="FILE_SCRIPT")
|
|
# op.is_object = is_object
|
|
op = row.operator("arm.new_wasm")
|
|
# op.is_object = is_object
|
|
op = row.operator("arm.refresh_scripts", text="Refresh")
|
|
|
|
elif item.type_prop == 'UI Canvas':
|
|
item.name = item.canvas_name_prop
|
|
|
|
row = layout.row(align=True)
|
|
row.alignment = 'EXPAND'
|
|
column = row.column(align=True)
|
|
column.alignment = 'EXPAND'
|
|
if item.canvas_name_prop == '':
|
|
column.enabled = False
|
|
op = column.operator("arm.edit_canvas", icon="FILE_SCRIPT")
|
|
op.is_object = is_object
|
|
op = row.operator("arm.new_canvas")
|
|
op.is_object = is_object
|
|
op = row.operator("arm.refresh_canvas_list", text="Refresh")
|
|
|
|
row = layout.row()
|
|
row.prop_search(item, "canvas_name_prop", bpy.data.worlds['Arm'], "arm_canvas_list", text="Canvas")
|
|
|
|
elif item.type_prop == 'Logic Nodes':
|
|
# Row for buttons
|
|
row = layout.row(align=True)
|
|
row.alignment = 'EXPAND'
|
|
# New
|
|
column = row.column(align=True)
|
|
column.alignment = 'EXPAND'
|
|
op = column.operator("arm.new_treenode", text="New Node Tree", icon="ADD")
|
|
op.is_object = is_object
|
|
# At least one check is active Logic Node Editor
|
|
is_check_logic_node_editor = False
|
|
context_screen = bpy.context.screen
|
|
# Loop for all spaces
|
|
if context_screen is not None:
|
|
areas = context_screen.areas
|
|
for area in areas:
|
|
for space in area.spaces:
|
|
if space.type == 'NODE_EDITOR':
|
|
if space.tree_type == 'ArmLogicTreeType' and space.node_tree is not None:
|
|
is_check_logic_node_editor = True
|
|
break
|
|
if is_check_logic_node_editor:
|
|
break
|
|
# Edit
|
|
column = row.column(align=True)
|
|
column.alignment = 'EXPAND'
|
|
if item.node_tree_prop is None:
|
|
column.enabled = False
|
|
else:
|
|
column.enabled = is_check_logic_node_editor
|
|
op = column.operator("arm.edit_treenode", text="Edit Node Tree", icon="NODETREE")
|
|
op.is_object = is_object
|
|
# Get from Node Tree Editor
|
|
column = row.column(align=True)
|
|
column.alignment = 'EXPAND'
|
|
if item is None:
|
|
column.enabled = False
|
|
else:
|
|
column.enabled = is_check_logic_node_editor
|
|
op = column.operator("arm.get_treenode", text="From Node Editor", icon="IMPORT")
|
|
op.is_object = is_object
|
|
|
|
# Row for search
|
|
row = layout.row()
|
|
row.prop_search(item, "node_tree_prop", bpy.data, "node_groups", text="Tree")
|
|
|
|
if item.type_prop == 'Haxe Script' or item.type_prop == 'Bundled Script':
|
|
# Props
|
|
if item.arm_traitpropslist:
|
|
layout.label(text="Trait Properties:")
|
|
if item.arm_traitpropswarnings:
|
|
box = layout.box()
|
|
box.label(text=f"Warnings ({len(item.arm_traitpropswarnings)}):", icon="ERROR")
|
|
|
|
for warning in item.arm_traitpropswarnings:
|
|
box.label(text=warning.warning)
|
|
|
|
propsrow = layout.row()
|
|
propsrows = max(len(item.arm_traitpropslist), 6)
|
|
row = layout.row()
|
|
row.template_list("ARM_UL_PropList", "The_List", item, "arm_traitpropslist", item, "arm_traitpropslist_index", rows=propsrows)
|
|
|
|
def register():
|
|
global icons_dict
|
|
bpy.utils.register_class(ArmTraitListItem)
|
|
bpy.utils.register_class(ARM_UL_TraitList)
|
|
bpy.utils.register_class(ArmTraitListNewItem)
|
|
bpy.utils.register_class(ArmTraitListDeleteItem)
|
|
bpy.utils.register_class(ArmTraitListDeleteItemScene)
|
|
bpy.utils.register_class(ArmTraitListMoveItem)
|
|
bpy.utils.register_class(ArmEditScriptButton)
|
|
bpy.utils.register_class(ArmEditBundledScriptButton)
|
|
bpy.utils.register_class(ArmoryGenerateNavmeshButton)
|
|
bpy.utils.register_class(ArmEditCanvasButton)
|
|
bpy.utils.register_class(ArmNewScriptDialog)
|
|
bpy.utils.register_class(ArmNewTreeNodeDialog)
|
|
bpy.utils.register_class(ArmEditTreeNodeDialog)
|
|
bpy.utils.register_class(ArmGetTreeNodeDialog)
|
|
bpy.utils.register_class(ArmNewCanvasDialog)
|
|
bpy.utils.register_class(ArmNewWasmButton)
|
|
bpy.utils.register_class(ArmRefreshScriptsButton)
|
|
bpy.utils.register_class(ArmRefreshCanvasListButton)
|
|
bpy.utils.register_class(ARM_PT_TraitPanel)
|
|
bpy.utils.register_class(ARM_PT_SceneTraitPanel)
|
|
bpy.utils.register_class(ARM_OT_CopyTraitsFromActive)
|
|
|
|
bpy.types.Object.arm_traitlist = CollectionProperty(type=ArmTraitListItem)
|
|
bpy.types.Object.arm_traitlist_index = IntProperty(name="Index for arm_traitlist", default=0)
|
|
bpy.types.Scene.arm_traitlist = CollectionProperty(type=ArmTraitListItem)
|
|
bpy.types.Scene.arm_traitlist_index = IntProperty(name="Index for arm_traitlist", default=0)
|
|
|
|
icons_dict = bpy.utils.previews.new()
|
|
icons_dir = os.path.join(os.path.dirname(__file__), "custom_icons")
|
|
icons_dict.load("haxe", os.path.join(icons_dir, "haxe.png"), 'IMAGE')
|
|
icons_dict.load("wasm", os.path.join(icons_dir, "wasm.png"), 'IMAGE')
|
|
|
|
def unregister():
|
|
global icons_dict
|
|
bpy.utils.unregister_class(ARM_OT_CopyTraitsFromActive)
|
|
bpy.utils.unregister_class(ArmTraitListItem)
|
|
bpy.utils.unregister_class(ARM_UL_TraitList)
|
|
bpy.utils.unregister_class(ArmTraitListNewItem)
|
|
bpy.utils.unregister_class(ArmTraitListDeleteItem)
|
|
bpy.utils.unregister_class(ArmTraitListDeleteItemScene)
|
|
bpy.utils.unregister_class(ArmTraitListMoveItem)
|
|
bpy.utils.unregister_class(ArmEditScriptButton)
|
|
bpy.utils.unregister_class(ArmEditBundledScriptButton)
|
|
bpy.utils.unregister_class(ArmoryGenerateNavmeshButton)
|
|
bpy.utils.unregister_class(ArmEditCanvasButton)
|
|
bpy.utils.unregister_class(ArmNewScriptDialog)
|
|
bpy.utils.unregister_class(ArmGetTreeNodeDialog)
|
|
bpy.utils.unregister_class(ArmEditTreeNodeDialog)
|
|
bpy.utils.unregister_class(ArmNewTreeNodeDialog)
|
|
bpy.utils.unregister_class(ArmNewCanvasDialog)
|
|
bpy.utils.unregister_class(ArmNewWasmButton)
|
|
bpy.utils.unregister_class(ArmRefreshScriptsButton)
|
|
bpy.utils.unregister_class(ArmRefreshCanvasListButton)
|
|
bpy.utils.unregister_class(ARM_PT_TraitPanel)
|
|
bpy.utils.unregister_class(ARM_PT_SceneTraitPanel)
|
|
bpy.utils.previews.remove(icons_dict)
|