import json import os import shutil import subprocess from typing import Union import webbrowser from bpy.types import NodeTree from bpy.props import * import bpy.utils.previews import arm.make as make from arm.props_traits_props import * import arm.proxy as proxy import arm.ui_icons as ui_icons import arm.utils import arm.write_data as write_data if arm.is_reload(__name__): arm.make = arm.reload_module(arm.make) arm.props_traits_props = arm.reload_module(arm.props_traits_props) from arm.props_traits_props import * proxy = arm.reload_module(proxy) ui_icons = arm.reload_module(ui_icons) arm.utils = arm.reload_module(arm.utils) arm.write_data = arm.reload_module(arm.write_data) else: arm.enable_reload(__name__) ICON_HAXE = ui_icons.get_id('haxe') ICON_NODES = 'NODETREE' ICON_CANVAS = 'NODE_COMPOSITING' ICON_BUNDLED = ui_icons.get_id('bundle') ICON_WASM = ui_icons.get_id('wasm') # Pay attention to the ID number parameter for backward compatibility! # This is important if the enum is reordered or the string identifier # is changed as the number is what's stored in the blend file PROP_TYPES_ENUM = [ ('Haxe Script', 'Haxe', 'Haxe script', ICON_HAXE, 0), ('Logic Nodes', 'Nodes', 'Logic nodes (visual scripting)', ICON_NODES, 4), ('UI Canvas', 'UI', 'User interface', ICON_CANVAS, 2), ('Bundled Script', 'Bundled', 'Premade script with common functionality', ICON_BUNDLED, 3), ('WebAssembly', 'Wasm', 'WebAssembly', ICON_WASM, 1) ] 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): def poll_node_trees(self, tree: NodeTree): """Ensure that only logic node trees show up as node traits""" return tree.bl_idname == 'ArmLogicTreeType' 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(name="Type", items=PROP_TYPES_ENUM) 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, poll=poll_node_trees) 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): """List of traits.""" def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): layout.use_property_split = False custom_icon = "NONE" custom_icon_value = 0 if item.type_prop == "Haxe Script": custom_icon_value = ICON_HAXE elif item.type_prop == "WebAssembly": custom_icon_value = ICON_WASM elif item.type_prop == "UI Canvas": custom_icon = "NODE_COMPOSITING" elif item.type_prop == "Bundled Script": custom_icon_value = ICON_BUNDLED elif item.type_prop == "Logic Nodes": custom_icon = 'NODETREE' if self.layout_type in {'DEFAULT', 'COMPACT'}: row = layout.row() row.separator(factor=0.1) row.prop(item, "enabled_prop") # Display " " for props without a name to right-align the # fake_user button row.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) row = layout.row(align=True) row.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 = "Add Trait" bl_description = "Add a new trait item to the list" is_object: BoolProperty(name="Is Object Trait", description="Whether this trait belongs to an object or a scene", default=False) type_prop: EnumProperty(name="Type", items=PROP_TYPES_ENUM) # Show more options when invoked from the operator search menu invoked_by_search: BoolProperty(name="", default=True) def invoke(self, context, event): wm = context.window_manager return wm.invoke_props_dialog(self, width=400) def draw(self, context): layout = self.layout if self.invoked_by_search: row = layout.row() row.prop(self, "is_object") row = layout.row() row.scale_y = 1.3 row.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 = "Remove Trait" 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(os.path.join(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 = os.path.join(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() item = obj.arm_traitlist[obj.arm_traitlist_index] pkg = arm.utils.safestr(bpy.data.worlds['Arm'].arm_project_package) source_hx_path = os.path.join(sdk_path, 'armory', 'Sources', 'armory', 'trait', item.class_name_prop + '.hx') target_dir = os.path.join(project_path, 'Sources', pkg) target_hx_path = os.path.join(target_dir, item.class_name_prop + '.hx') if not os.path.isfile(target_hx_path): if not os.path.exists(target_dir): os.makedirs(target_dir) # Rewrite package and copy with open(source_hx_path, encoding="utf-8") as sf: sf.readline() with open(target_hx_path, 'w', encoding="utf-8") as tf: tf.write('package ' + pkg + ';\n') shutil.copyfileobj(sf, tf) arm.utils.fetch_script_names() # From bundled to script item.type_prop = 'Haxe Script' # Open the trait in the code editor 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() 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): obj = bpy.context.object draw_traits_panel(self.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): obj = bpy.context.scene draw_traits_panel(self.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_panel(layout: bpy.types.UILayout, obj: Union[bpy.types.Object, bpy.types.Scene], is_object: bool) -> None: layout.use_property_split = True layout.use_property_decorate = False # Make the list bigger when there are a few traits num_rows = 2 if len(obj.arm_traitlist) > 1: num_rows = 4 row = layout.row() row.template_list("ARM_UL_TraitList", "The_List", obj, "arm_traitlist", obj, "arm_traitlist_index", rows=num_rows) col = row.column(align=True) op = col.operator("arm_traitlist.new_item", icon='ADD', text="") op.invoked_by_search = False op.is_object = is_object if is_object: op = col.operator("arm_traitlist.delete_item", icon='REMOVE', text="") else: op = col.operator("arm_traitlist.delete_item_scene", icon='REMOVE', text="") 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 # Draw trait specific content if obj.arm_traitlist_index >= 0 and len(obj.arm_traitlist) > 0: item = obj.arm_traitlist[obj.arm_traitlist_index] row = layout.row(align=True) row.alignment = 'EXPAND' row.scale_y = 1.2 if item.type_prop == 'Haxe Script' or item.type_prop == 'Bundled Script': if item.type_prop == 'Haxe Script': row.operator("arm.new_script", icon="FILE_NEW").is_object = is_object column = row.column(align=True) column.enabled = item.class_name_prop != '' column.operator("arm.edit_script", icon="FILE_SCRIPT").is_object = is_object # Bundled scripts else: if item.class_name_prop == 'NavMesh': row.operator("arm.generate_navmesh", icon="UV_VERTEXSEL") else: row.operator("arm.edit_bundled_script", icon="FILE_SCRIPT").is_object = is_object row.operator("arm.refresh_scripts", text="Refresh", icon="FILE_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.operator("arm.new_wasm", icon="FILE_NEW") row.operator("arm.refresh_scripts", text="Refresh", icon="FILE_REFRESH") row = layout.row() row.prop_search(item, "webassembly_prop", bpy.data.worlds['Arm'], "arm_wasm_list", text="Module") elif item.type_prop == 'UI Canvas': item.name = item.canvas_name_prop row.operator("arm.new_canvas", icon="FILE_NEW").is_object = is_object column = row.column(align=True) column.enabled = item.canvas_name_prop != '' column.operator("arm.edit_canvas", icon="NODE_COMPOSITING").is_object = is_object row.operator("arm.refresh_canvas_list", text="Refresh", icon="FILE_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': # Check if there is at least one active Logic Node Editor is_editor_active = False if bpy.context.screen is not None: areas = bpy.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_editor_active = True break if is_editor_active: break row.operator("arm.new_treenode", text="New Tree", icon="ADD").is_object = is_object column = row.column(align=True) column.enabled = is_editor_active and item.node_tree_prop is not None column.operator("arm.edit_treenode", text="Edit Tree", icon="NODETREE").is_object = is_object column = row.column(align=True) column.enabled = is_editor_active and item is not None column.operator("arm.get_treenode", text="From Editor", icon="IMPORT").is_object = is_object row = layout.row() row.prop_search(item, "node_tree_prop", bpy.data, "node_groups", text="Tree") # ===================== # Draw trait properties if item.type_prop == 'Haxe Script' or item.type_prop == 'Bundled Script': 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") col = box.column(align=True) for warning in item.arm_traitpropswarnings: col.label(text=f'"{warning.propName}": {warning.warning}') 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(): 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) def unregister(): 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)