import json import os import shutil import bpy from bpy.props import * from arm.lightmapper.panels import scene import arm.api import arm.assets as assets from arm.exporter import ArmoryExporter import arm.log as log import arm.logicnode.replacement import arm.make as make import arm.make_state as state import arm.props as props import arm.props_properties import arm.props_traits import arm.nodes_logic import arm.proxy import arm.ui_icons as ui_icons import arm.utils if arm.is_reload(__name__): arm.api = arm.reload_module(arm.api) assets = arm.reload_module(assets) arm.exporter = arm.reload_module(arm.exporter) from arm.exporter import ArmoryExporter log = arm.reload_module(log) arm.logicnode.replacement = arm.reload_module(arm.logicnode.replacement) make = arm.reload_module(make) state = arm.reload_module(state) props = arm.reload_module(props) arm.props_properties = arm.reload_module(arm.props_properties) arm.props_traits = arm.reload_module(arm.props_traits) arm.nodes_logic = arm.reload_module(arm.nodes_logic) arm.proxy = arm.reload_module(arm.proxy) ui_icons = arm.reload_module(ui_icons) arm.utils = arm.reload_module(arm.utils) else: arm.enable_reload(__name__) class ARM_PT_ObjectPropsPanel(bpy.types.Panel): """Menu in object region.""" bl_label = "Armory Props" 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 if obj == None: return col = layout.column() col.prop(obj, 'arm_export') if not obj.arm_export: return col.prop(obj, 'arm_spawn') col.prop(obj, 'arm_mobile') col.prop(obj, 'arm_animation_enabled') if obj.type == 'MESH': layout.prop(obj, 'arm_instanced') wrd = bpy.data.worlds['Arm'] layout.prop_search(obj, "arm_tilesheet", wrd, "arm_tilesheetlist", text="Tilesheet") if obj.arm_tilesheet != '': selected_ts = None for ts in wrd.arm_tilesheetlist: if ts.name == obj.arm_tilesheet: selected_ts = ts break layout.prop_search(obj, "arm_tilesheet_action", selected_ts, "arm_tilesheetactionlist", text="Action") # Properties list arm.props_properties.draw_properties(layout, obj) # Lightmapping props if obj.type == "MESH": row = layout.row(align=True) scene = bpy.context.scene if scene == None: return row.prop(obj.TLM_ObjectProperties, "tlm_mesh_lightmap_use") if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: row = layout.row() row.prop(obj.TLM_ObjectProperties, "tlm_use_default_channel") if not obj.TLM_ObjectProperties.tlm_use_default_channel: row = layout.row() row.prop_search(obj.TLM_ObjectProperties, "tlm_uv_channel", obj.data, "uv_layers", text='UV Channel') row = layout.row() row.prop(obj.TLM_ObjectProperties, "tlm_mesh_lightmap_resolution") if obj.TLM_ObjectProperties.tlm_use_default_channel: row = layout.row() row.prop(obj.TLM_ObjectProperties, "tlm_mesh_lightmap_unwrap_mode") row = layout.row() if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": if scene.TLM_AtlasListItem >= 0 and len(scene.TLM_AtlasList) > 0: row = layout.row() item = scene.TLM_AtlasList[scene.TLM_AtlasListItem] row.prop_search(obj.TLM_ObjectProperties, "tlm_atlas_pointer", scene, "TLM_AtlasList", text='Atlas Group') row = layout.row() else: row = layout.label(text="Add Atlas Groups from the scene lightmapping settings.") row = layout.row() else: row = layout.row() row.prop(obj.TLM_ObjectProperties, "tlm_postpack_object") row = layout.row() if obj.TLM_ObjectProperties.tlm_postpack_object and obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode != "AtlasGroupA": if scene.TLM_PostAtlasListItem >= 0 and len(scene.TLM_PostAtlasList) > 0: row = layout.row() item = scene.TLM_PostAtlasList[scene.TLM_PostAtlasListItem] row.prop_search(obj.TLM_ObjectProperties, "tlm_postatlas_pointer", scene, "TLM_PostAtlasList", text='Atlas Group') row = layout.row() else: row = layout.label(text="Add Atlas Groups from the scene lightmapping settings.") row = layout.row() row.prop(obj.TLM_ObjectProperties, "tlm_mesh_unwrap_margin") row = layout.row() row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filter_override") row = layout.row() if obj.TLM_ObjectProperties.tlm_mesh_filter_override: row = layout.row(align=True) row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_mode") row = layout.row(align=True) if obj.TLM_ObjectProperties.tlm_mesh_filtering_mode == "Gaussian": row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_gaussian_strength") row = layout.row(align=True) row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_iterations") elif obj.TLM_ObjectProperties.tlm_mesh_filtering_mode == "Box": row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_box_strength") row = layout.row(align=True) row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_iterations") elif obj.TLM_ObjectProperties.tlm_mesh_filtering_mode == "Bilateral": row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_bilateral_diameter") row = layout.row(align=True) row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_bilateral_color_deviation") row = layout.row(align=True) row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_bilateral_coordinate_deviation") row = layout.row(align=True) row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_iterations") else: row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_median_kernel", expand=True) row = layout.row(align=True) row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_iterations") class ARM_PT_ModifiersPropsPanel(bpy.types.Panel): bl_label = "Armory Props" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "modifier" bl_options = {'DEFAULT_CLOSED'} def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False obj = bpy.context.object if obj == None: return layout.operator("arm.invalidate_cache") class ARM_PT_ParticlesPropsPanel(bpy.types.Panel): bl_label = "Armory Props" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "particle" bl_options = {'DEFAULT_CLOSED'} def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False obj = bpy.context.particle_system if obj == None: return layout.prop(obj.settings, 'arm_loop') layout.prop(obj.settings, 'arm_count_mult') class ARM_PT_PhysicsPropsPanel(bpy.types.Panel): bl_label = "Armory Props" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "physics" def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False obj = bpy.context.object if obj == None: return rb = obj.rigid_body if rb is not None: col = layout.column() row = col.row() row.alignment = 'RIGHT' rb_type = 'Dynamic' if ArmoryExporter.rigid_body_static(rb): rb_type = 'Static' if rb.kinematic: rb_type = 'Kinematic' row.label(text=(f'Rigid Body Export Type: {rb_type}'), icon='AUTO') layout.prop(obj, 'arm_rb_linear_factor') layout.prop(obj, 'arm_rb_angular_factor') layout.prop(obj, 'arm_rb_trigger') layout.prop(obj, 'arm_rb_ccd') if obj.soft_body is not None: layout.prop(obj, 'arm_soft_body_margin') if obj.rigid_body_constraint is not None: layout.prop(obj, 'arm_relative_physics_constraint') # Menu in data region class ARM_PT_DataPropsPanel(bpy.types.Panel): bl_label = "Armory Props" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "data" bl_options = {'DEFAULT_CLOSED'} def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False obj = bpy.context.object if obj == None: return wrd = bpy.data.worlds['Arm'] if obj.type == 'CAMERA': layout.prop(obj.data, 'arm_frustum_culling') elif obj.type == 'MESH' or obj.type == 'FONT' or obj.type == 'META': layout.prop(obj.data, 'arm_dynamic_usage') layout.operator("arm.invalidate_cache") elif obj.type == 'LIGHT': layout.prop(obj.data, 'arm_clip_start') layout.prop(obj.data, 'arm_clip_end') layout.prop(obj.data, 'arm_fov') layout.prop(obj.data, 'arm_shadows_bias') layout.prop(wrd, 'arm_light_ies_texture') layout.prop(wrd, 'arm_light_clouds_texture') elif obj.type == 'SPEAKER': layout.prop(obj.data, 'arm_play_on_start') layout.prop(obj.data, 'arm_loop') layout.prop(obj.data, 'arm_stream') elif obj.type == 'ARMATURE': layout.prop(obj.data, 'arm_autobake') pass class ARM_PT_WorldPropsPanel(bpy.types.Panel): bl_label = "Armory World Properties" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "world" def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False world = context.world if world is None: return layout.prop(world, 'arm_use_clouds') col = layout.column(align=True) col.enabled = world.arm_use_clouds col.prop(world, 'arm_darken_clouds') col.prop(world, 'arm_clouds_lower') col.prop(world, 'arm_clouds_upper') col.prop(world, 'arm_clouds_precipitation') col.prop(world, 'arm_clouds_secondary') col.prop(world, 'arm_clouds_wind') col.prop(world, 'arm_clouds_steps') class ARM_PT_ScenePropsPanel(bpy.types.Panel): bl_label = "Armory Props" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" bl_options = {'DEFAULT_CLOSED'} def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False scene = bpy.context.scene if scene == None: return row = layout.row() column = row.column() row.prop(scene, 'arm_export') class InvalidateCacheButton(bpy.types.Operator): """Delete cached mesh data""" bl_idname = "arm.invalidate_cache" bl_label = "Invalidate Cache" def execute(self, context): context.object.data.arm_cached = False return{'FINISHED'} class InvalidateMaterialCacheButton(bpy.types.Operator): """Delete cached material data""" bl_idname = "arm.invalidate_material_cache" bl_label = "Invalidate Cache" def execute(self, context): context.material.arm_cached = False context.material.signature = '' return{'FINISHED'} class ARM_OT_NewCustomMaterial(bpy.types.Operator): bl_idname = "arm.new_custom_material" bl_label = "New Custom Material" bl_description = "Add a new custom material. This will create all the necessary files and folders" def poll_mat_name(self, context): project_dir = arm.utils.get_fp() shader_dir_dst = os.path.join(project_dir, 'Shaders') mat_name = arm.utils.safestr(self.mat_name) self.mat_exists = os.path.isdir(os.path.join(project_dir, 'Bundled', mat_name)) vert_exists = os.path.isfile(os.path.join(shader_dir_dst, f'{mat_name}.vert.glsl')) frag_exists = os.path.isfile(os.path.join(shader_dir_dst, f'{mat_name}.frag.glsl')) self.shader_exists = vert_exists or frag_exists mat_name: StringProperty( name='Material Name', description='The name of the new material', default='MyCustomMaterial', update=poll_mat_name) mode: EnumProperty( name='Target RP', description='Choose for which render path mode the new material is created', default='deferred', items=[('deferred', 'Deferred', 'Create the material for a deferred render path'), ('forward', 'Forward', 'Create the material for a forward render path')]) mat_exists: BoolProperty( name='Material Already Exists', default=False, options={'HIDDEN', 'SKIP_SAVE'}) shader_exists: BoolProperty( name='Shaders Already Exist', default=False, options={'HIDDEN', 'SKIP_SAVE'}) def invoke(self, context, event): if not bpy.data.is_saved: self.report({'INFO'}, "Please save your file first") return {"CANCELLED"} # Try to set deferred/forward based on the selected render path try: self.mode = 'forward' if arm.utils.get_rp().rp_renderer == 'Forward' else 'deferred' except IndexError: # No render path, use default (deferred) pass self.poll_mat_name(context) wm = context.window_manager return wm.invoke_props_dialog(self, width=300) def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False layout.prop(self, 'mat_name') layout.prop(self, 'mode', expand=True) if self.mat_exists: box = layout.box() box.alert = True col = box.column(align=True) col.label(text='A custom material with that name already exists,', icon='ERROR') col.label(text='clicking on \'OK\' will override the material!', icon='BLANK1') if self.shader_exists: box = layout.box() box.alert = True col = box.column(align=True) col.label(text='Shader file(s) with that name already exists,', icon='ERROR') col.label(text='clicking on \'OK\' will override the shader(s)!', icon='BLANK1') def execute(self, context): if self.mat_name == '': return {'CANCELLED'} project_dir = arm.utils.get_fp() shader_dir_src = os.path.join(arm.utils.get_sdk_path(), 'armory', 'Shaders', 'custom_mat_presets') shader_dir_dst = os.path.join(project_dir, 'Shaders') mat_name = arm.utils.safestr(self.mat_name) mat_dir = os.path.join(project_dir, 'Bundled', mat_name) os.makedirs(mat_dir, exist_ok=True) os.makedirs(shader_dir_dst, exist_ok=True) # Shader data if self.mode == 'forward': col_attachments = ['RGBA64'] constants = [{'link': '_worldViewProjectionMatrix', 'name': 'WVP', 'type': 'mat4'}] vertex_elems = [{'name': 'pos', 'data': 'short4norm'}] else: col_attachments = ['RGBA64', 'RGBA64'] constants = [ {'link': '_worldViewProjectionMatrix', 'name': 'WVP', 'type': 'mat4'}, {'link': '_normalMatrix', 'name': 'N', 'type': 'mat3'} ] vertex_elems = [ {'name': 'pos', 'data': 'short4norm'}, {'name': 'nor', 'data': 'short2norm'} ] con = { 'color_attachments': col_attachments, 'compare_mode': 'less', 'constants': constants, 'cull_mode': 'clockwise', 'depth_write': True, 'fragment_shader': f'{mat_name}.frag', 'name': 'mesh', 'texture_units': [], 'vertex_shader': f'{mat_name}.vert', 'vertex_elements': vertex_elems } data = { 'shader_datas': [{ 'contexts': [con], 'name': f'{mat_name}' }] } # Save shader data file with open(os.path.join(mat_dir, f'{mat_name}.json'), 'w') as datafile: json.dump(data, datafile, indent=4, sort_keys=True) # Copy preset shaders to project if self.mode == 'forward': shutil.copy(os.path.join(shader_dir_src, 'custom_mat_forward.frag.glsl'), os.path.join(shader_dir_dst, f'{mat_name}.frag.glsl')) shutil.copy(os.path.join(shader_dir_src, 'custom_mat_forward.vert.glsl'), os.path.join(shader_dir_dst, f'{mat_name}.vert.glsl')) else: shutil.copy(os.path.join(shader_dir_src, 'custom_mat_deferred.frag.glsl'), os.path.join(shader_dir_dst, f'{mat_name}.frag.glsl')) shutil.copy(os.path.join(shader_dir_src, 'custom_mat_deferred.vert.glsl'), os.path.join(shader_dir_dst, f'{mat_name}.vert.glsl')) # True if called from the material properties tab, else it was called from the search menu if hasattr(context, 'material') and context.material is not None: context.material.arm_custom_material = mat_name return{'FINISHED'} class ARM_PT_MaterialPropsPanel(bpy.types.Panel): bl_label = "Armory Props" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "material" def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False mat = bpy.context.material if mat is None: return layout.prop(mat, 'arm_cast_shadow') columnb = layout.column() wrd = bpy.data.worlds['Arm'] columnb.enabled = len(wrd.arm_rplist) > 0 and arm.utils.get_rp().rp_renderer == 'Forward' columnb.prop(mat, 'arm_receive_shadow') layout.prop(mat, 'arm_ignore_irradiance') layout.prop(mat, 'arm_two_sided') columnb = layout.column() columnb.enabled = not mat.arm_two_sided columnb.prop(mat, 'arm_cull_mode') layout.prop(mat, 'arm_material_id') layout.prop(mat, 'arm_overlay') layout.prop(mat, 'arm_decal') layout.prop(mat, 'arm_discard') columnb = layout.column() columnb.enabled = mat.arm_discard columnb.prop(mat, 'arm_discard_opacity') columnb.prop(mat, 'arm_discard_opacity_shadows') row = layout.row(align=True) row.prop(mat, 'arm_custom_material') row.operator('arm.new_custom_material', text='', icon='ADD') layout.prop(mat, 'arm_skip_context') layout.prop(mat, 'arm_particle_fade') layout.prop(mat, 'arm_billboard') layout.operator("arm.invalidate_material_cache") class ARM_PT_MaterialDriverPropsPanel(bpy.types.Panel): """Per-material properties for custom render path drivers""" bl_label = "Armory Driver Properties" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "material" @classmethod def poll(cls, context): mat = context.material if mat is None: return False wrd = bpy.data.worlds['Arm'] if wrd.arm_rplist_index < 0 or len(wrd.arm_rplist) == 0: return False if len(arm.api.drivers) == 0: return False rpdat = wrd.arm_rplist[wrd.arm_rplist_index] return rpdat.rp_driver != 'Armory' and arm.api.drivers[rpdat.rp_driver]['draw_mat_props'] is not None def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] rpdat = wrd.arm_rplist[wrd.arm_rplist_index] arm.api.drivers[rpdat.rp_driver]['draw_mat_props'](layout, context.material) class ARM_PT_MaterialBlendingPropsPanel(bpy.types.Panel): bl_label = "Blending" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "material" bl_options = {'DEFAULT_CLOSED'} bl_parent_id = "ARM_PT_MaterialPropsPanel" def draw_header(self, context): if context.material is None: return self.layout.prop(context.material, 'arm_blending', text="") def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False mat = bpy.context.material if mat is None: return flow = layout.grid_flow() flow.enabled = mat.arm_blending col = flow.column(align=True) col.prop(mat, 'arm_blending_source') col.prop(mat, 'arm_blending_destination') col.prop(mat, 'arm_blending_operation') flow.separator() col = flow.column(align=True) col.prop(mat, 'arm_blending_source_alpha') col.prop(mat, 'arm_blending_destination_alpha') col.prop(mat, 'arm_blending_operation_alpha') class ARM_PT_ArmoryPlayerPanel(bpy.types.Panel): bl_label = "Armory Player" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "render" def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] row = layout.row(align=True) row.alignment = 'EXPAND' row.scale_y = 1.3 if state.proc_play is None and state.proc_build is None: row.operator("arm.play", icon="PLAY") else: row.operator("arm.stop", icon="MESH_PLANE") row.operator("arm.clean_menu", icon="BRUSH_DATA") box = layout.box() box.prop(wrd, 'arm_runtime') box.prop(wrd, 'arm_play_camera') box.prop(wrd, 'arm_play_scene') if log.num_warnings > 0: box = layout.box() box.alert = True col = box.column(align=True) warnings = 'warnings' if log.num_warnings > 1 else 'warning' col.label(text=f'{log.num_warnings} {warnings} occurred during compilation!', icon='ERROR') # Blank icon to achieve the same indentation as the line before # prevent showing "open console" twice: if log.num_errors == 0: col.label(text='Please open the console to get more information.', icon='BLANK1') if log.num_errors > 0: box = layout.box() box.alert = True # Less spacing between lines col = box.column(align=True) errors = 'errors' if log.num_errors > 1 else 'error' col.label(text=f'{log.num_errors} {errors} occurred during compilation!', icon='CANCEL') # Blank icon to achieve the same indentation as the line before col.label(text='Please open the console to get more information.', icon='BLANK1') class ARM_PT_ArmoryExporterPanel(bpy.types.Panel): bl_label = "Armory Exporter" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "render" bl_options = {'DEFAULT_CLOSED'} def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] row = layout.row(align=True) row.alignment = 'EXPAND' row.scale_y = 1.3 row.operator("arm.build_project", icon="MOD_BUILD") # row.operator("arm.patch_project") row.operator("arm.publish_project", icon="EXPORT") rows = 2 if len(wrd.arm_exporterlist) > 1: rows = 4 row = layout.row() row.template_list("ARM_UL_ExporterList", "The_List", wrd, "arm_exporterlist", wrd, "arm_exporterlist_index", rows=rows) col = row.column(align=True) col.operator("arm_exporterlist.new_item", icon='ADD', text="") col.operator("arm_exporterlist.delete_item", icon='REMOVE', text="") col.menu("ARM_MT_ExporterListSpecials", icon='DOWNARROW_HLT', text="") if len(wrd.arm_exporterlist) > 1: col.separator() op = col.operator("arm_exporterlist.move_item", icon='TRIA_UP', text="") op.direction = 'UP' op = col.operator("arm_exporterlist.move_item", icon='TRIA_DOWN', text="") op.direction = 'DOWN' if wrd.arm_exporterlist_index >= 0 and len(wrd.arm_exporterlist) > 0: item = wrd.arm_exporterlist[wrd.arm_exporterlist_index] box = layout.box().column() box.prop(item, 'arm_project_target') if item.arm_project_target == 'custom': box.prop(item, 'arm_project_khamake') box.prop(item, arm.utils.target_to_gapi(item.arm_project_target)) wrd.arm_rpcache_list.clear() # Make UIList work with prop_search() for i in wrd.arm_rplist: wrd.arm_rpcache_list.add().name = i.name box.prop_search(item, "arm_project_rp", wrd, "arm_rpcache_list", text="Render Path") box.prop_search(item, 'arm_project_scene', bpy.data, 'scenes', text='Scene') layout.separator() col = layout.column(align=True) col.prop(wrd, 'arm_project_name') col.prop(wrd, 'arm_project_package') col.prop(wrd, 'arm_project_bundle') col = layout.column(align=True) col.prop(wrd, 'arm_project_version') col.prop(wrd, 'arm_project_version_autoinc') col = layout.column() col.prop(wrd, 'arm_project_icon') col = layout.column(heading='Code Output') col.prop(wrd, 'arm_dce') col.prop(wrd, 'arm_compiler_inline') col.prop(wrd, 'arm_minify_js') col = layout.column(heading='Data') col.prop(wrd, 'arm_minimize') col.prop(wrd, 'arm_optimize_data') col.prop(wrd, 'arm_asset_compression') col.prop(wrd, 'arm_single_data_file') class ExporterTargetSettingsMixin: """Mixin for common exporter setting subpanel functionality. Panels that inherit from this mixin need to have a arm_target variable for polling.""" bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = 'render' bl_parent_id = 'ARM_PT_ArmoryExporterPanel' # Override this in sub classes arm_panel = '' @classmethod def poll(cls, context): wrd = bpy.data.worlds['Arm'] if (len(wrd.arm_exporterlist) > 0) and (wrd.arm_exporterlist_index >= 0): item = wrd.arm_exporterlist[wrd.arm_exporterlist_index] return item.arm_project_target == cls.arm_target return False def draw_header(self, context): self.layout.label(text='', icon='SETTINGS') class ARM_PT_ArmoryExporterAndroidSettingsPanel(ExporterTargetSettingsMixin, bpy.types.Panel): bl_label = "Android Settings" arm_target = 'android-hl' # See ExporterTargetSettingsMixin def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] col = layout.column() col.prop(wrd, 'arm_winorient') col.prop(wrd, 'arm_project_android_sdk_compile') col.prop(wrd, 'arm_project_android_sdk_min') col.prop(wrd, 'arm_project_android_sdk_target') class ARM_PT_ArmoryExporterAndroidPermissionsPanel(bpy.types.Panel): bl_label = "Permissions" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "render" bl_options = {'DEFAULT_CLOSED'} bl_parent_id = "ARM_PT_ArmoryExporterAndroidSettingsPanel" def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] # Permission row = layout.row() rows = 2 if len(wrd.arm_exporter_android_permission_list) > 1: rows = 4 row.template_list("ARM_UL_Exporter_AndroidPermissionList", "The_List", wrd, "arm_exporter_android_permission_list", wrd, "arm_exporter_android_permission_list_index", rows=rows) col = row.column(align=True) col.operator("arm_exporter_android_permission_list.new_item", icon='ADD', text="") col.operator("arm_exporter_android_permission_list.delete_item", icon='REMOVE', text="") row = layout.row() if wrd.arm_exporter_android_permission_list_index >= 0 and len(wrd.arm_exporter_android_permission_list) > 0: item = wrd.arm_exporter_android_permission_list[wrd.arm_exporter_android_permission_list_index] row = layout.row() row.prop(item, 'arm_android_permissions') class ARM_PT_ArmoryExporterAndroidAbiPanel(bpy.types.Panel): bl_label = "Android ABI Filters" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "render" bl_options = { 'DEFAULT_CLOSED'} bl_parent_id = "ARM_PT_ArmoryExporterAndroidSettingsPanel" def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] # ABIs row = layout.row() rows = 2 if len(wrd.arm_exporter_android_abi_list) > 1: rows = 4 row.template_list("ARM_UL_Exporter_AndroidAbiList", "The_List", wrd, "arm_exporter_android_abi_list", wrd, "arm_exporter_android_abi_list_index", rows=rows) col = row.column(align=True) col.operator("arm_exporter_android_abi_list.new_item", icon='ADD', text="") col.operator("arm_exporter_android_abi_list.delete_item", icon='REMOVE', text="") row = layout.row() if wrd.arm_exporter_android_abi_list_index >= 0 and len(wrd.arm_exporter_android_abi_list) > 0: item = wrd.arm_exporter_android_abi_list[wrd.arm_exporter_android_abi_list_index] row = layout.row() row.prop(item, 'arm_android_abi') class ARM_PT_ArmoryExporterAndroidBuildAPKPanel(bpy.types.Panel): bl_label = "Building APK" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "render" bl_options = {'DEFAULT_CLOSED'} bl_parent_id = "ARM_PT_ArmoryExporterAndroidSettingsPanel" def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] path = arm.utils.get_android_sdk_root_path() col = layout.column() row = col.row() row.enabled = len(path) > 0 row.prop(wrd, 'arm_project_android_build_apk') row = col.row() row.enabled = wrd.arm_project_android_build_apk row.prop(wrd, 'arm_project_android_rename_apk') row = col.row() row.enabled = wrd.arm_project_android_build_apk and len(arm.utils.get_android_apk_copy_path()) > 0 row.prop(wrd, 'arm_project_android_copy_apk') row = col.row(align=True) row.prop(wrd, 'arm_project_android_list_avd') sub = row.column(align=True) sub.enabled = len(path) > 0 sub.operator('arm.update_list_android_emulator', text='', icon='FILE_REFRESH') sub = row.column(align=True) sub.enabled = len(path) > 0 and len(arm.utils.get_android_emulator_name()) > 0 sub.operator('arm.run_android_emulator', text='', icon='PLAY') row = col.row() row.enabled = arm.utils.get_project_android_build_apk() and len(arm.utils.get_android_emulator_name()) > 0 row.prop(wrd, 'arm_project_android_run_avd') class ARM_PT_ArmoryExporterHTML5SettingsPanel(ExporterTargetSettingsMixin, bpy.types.Panel): bl_label = "HTML5 Settings" arm_target = 'html5' # See ExporterTargetSettingsMixin def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] col = layout.column() col.prop(wrd, 'arm_project_html5_popupmenu_in_browser') row = col.row() row.enabled = len(arm.utils.get_html5_copy_path()) > 0 row.prop(wrd, 'arm_project_html5_copy') row = col.row() row.enabled = len(arm.utils.get_html5_copy_path()) > 0 and wrd.arm_project_html5_copy and len(arm.utils.get_link_web_server()) > 0 row.prop(wrd, 'arm_project_html5_start_browser') class ARM_PT_ArmoryExporterWindowsSettingsPanel(ExporterTargetSettingsMixin, bpy.types.Panel): bl_label = "Windows Settings" arm_target = 'windows-hl' # See ExporterTargetSettingsMixin def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] col = layout.column() row = col.row(align=True) row.prop(wrd, 'arm_project_win_list_vs') sub = row.column(align=True) sub.enabled = arm.utils.get_os_is_windows() sub.operator('arm.update_list_installed_vs', text='', icon='FILE_REFRESH') row = col.row() row.enabled = arm.utils.get_os_is_windows() row.prop(wrd, 'arm_project_win_build', text='After Publish') layout.separator() col = layout.column() col.enabled = arm.utils.get_os_is_windows() and wrd.arm_project_win_build != '0' and wrd.arm_project_win_build != '1' col.prop(wrd, 'arm_project_win_build_mode') col.prop(wrd, 'arm_project_win_build_arch') col.prop(wrd, 'arm_project_win_build_log') col.prop(wrd, 'arm_project_win_build_cpu') col.prop(wrd, 'arm_project_win_build_open') class ARM_PT_ArmoryProjectPanel(bpy.types.Panel): bl_label = "Armory Project" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "render" bl_options = {'DEFAULT_CLOSED'} def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False row = layout.row(align=True) row.operator("arm.open_editor", icon="DESKTOP") row.operator("arm.open_project_folder", icon="FILE_FOLDER") class ARM_PT_ProjectFlagsPanel(bpy.types.Panel): bl_label = "Flags" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "render" bl_parent_id = "ARM_PT_ArmoryProjectPanel" def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] col = layout.column(heading='Debug') col.prop(wrd, 'arm_verbose_output') col.prop(wrd, 'arm_cache_build') col = layout.column(heading='Runtime') col.prop(wrd, 'arm_live_patch') col.prop(wrd, 'arm_stream_scene') col.prop(wrd, 'arm_loadscreen') col.prop(wrd, 'arm_write_config') col = layout.column(heading='Renderer') col.prop(wrd, 'arm_batch_meshes') col.prop(wrd, 'arm_batch_materials') col.prop(wrd, 'arm_deinterleaved_buffers') col.prop(wrd, 'arm_export_tangents') col = layout.column(heading='Quality') col.prop(wrd, 'arm_texture_quality') col.prop(wrd, 'arm_sound_quality') class ARM_PT_ProjectFlagsDebugConsolePanel(bpy.types.Panel): bl_label = "Debug Console" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "render" bl_options = {'DEFAULT_CLOSED'} bl_parent_id = "ARM_PT_ProjectFlagsPanel" def draw_header(self, context): wrd = bpy.data.worlds['Arm'] self.layout.prop(wrd, 'arm_debug_console', text='') def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] col = layout.column() col.enabled = wrd.arm_debug_console col.prop(wrd, 'arm_debug_console_position') col.prop(wrd, 'arm_debug_console_scale') col.prop(wrd, 'arm_debug_console_visible') class ARM_PT_ProjectWindowPanel(bpy.types.Panel): bl_label = "Window" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "render" bl_options = {'DEFAULT_CLOSED'} bl_parent_id = "ARM_PT_ArmoryProjectPanel" def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] layout.prop(wrd, 'arm_winmode') col = layout.column(align=True) col.prop(wrd, 'arm_winresize') sub = col.column() sub.enabled = wrd.arm_winresize sub.prop(wrd, 'arm_winmaximize') col.enabled = True col.prop(wrd, 'arm_winminimize') layout.prop(wrd, 'arm_vsync') class ARM_PT_ProjectModulesPanel(bpy.types.Panel): bl_label = "Modules" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "render" bl_options = {'DEFAULT_CLOSED'} bl_parent_id = "ARM_PT_ArmoryProjectPanel" def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] layout.prop(wrd, 'arm_audio') layout.prop(wrd, 'arm_physics') if wrd.arm_physics != 'Disabled': layout.prop(wrd, 'arm_physics_engine') layout.prop(wrd, 'arm_navigation') if wrd.arm_navigation != 'Disabled': layout.prop(wrd, 'arm_navigation_engine') layout.prop(wrd, 'arm_ui') layout.prop_search(wrd, 'arm_khafile', bpy.data, 'texts', text='Khafile') layout.prop(wrd, 'arm_project_root') class ArmVirtualInputPanel(bpy.types.Panel): bl_label = "Armory Virtual Input" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "render" bl_options = {'DEFAULT_CLOSED'} def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False class ArmoryPlayButton(bpy.types.Operator): '''Launch player in new window''' bl_idname = 'arm.play' bl_label = 'Play' def invoke(self, context, event): if event.shift: state.is_play = True make.build_success() return{'FINISHED'} return self.execute(context) def execute(self, context): if state.proc_build != None: return {"CANCELLED"} # Compare version Blender and Armory (major, minor) if not arm.utils.compare_version_blender_arm(): self.report({'INFO'}, 'For Armory to work correctly, you need Blender 2.93 LTS.') if not arm.utils.check_saved(self): return {"CANCELLED"} if not arm.utils.check_sdkpath(self): return {"CANCELLED"} arm.utils.check_projectpath(None) arm.utils.check_default_props() assets.invalidate_enabled = False make.play() assets.invalidate_enabled = True return{'FINISHED'} class ArmoryStopButton(bpy.types.Operator): '''Stop currently running player''' bl_idname = 'arm.stop' bl_label = 'Stop' def execute(self, context): if state.proc_play != None: state.proc_play.terminate() state.proc_play = None elif state.proc_build != None: state.proc_build.terminate() state.proc_build = None return{'FINISHED'} class ArmoryBuildProjectButton(bpy.types.Operator): """Build and compile project""" bl_idname = 'arm.build_project' bl_label = 'Build' @classmethod def poll(cls, context): wrd = bpy.data.worlds['Arm'] return wrd.arm_exporterlist_index >= 0 and len(wrd.arm_exporterlist) > 0 def execute(self, context): # Compare version Blender and Armory (major, minor) if not arm.utils.compare_version_blender_arm(): self.report({'INFO'}, 'For Armory to work correctly, you need Blender 2.93 LTS.') if not arm.utils.check_saved(self): return {"CANCELLED"} if not arm.utils.check_sdkpath(self): return {"CANCELLED"} arm.utils.check_projectpath(self) arm.utils.check_default_props() wrd = bpy.data.worlds['Arm'] item = wrd.arm_exporterlist[wrd.arm_exporterlist_index] if item.arm_project_rp == '': item.arm_project_rp = wrd.arm_rplist[wrd.arm_rplist_index].name if item.arm_project_scene == None: item.arm_project_scene = context.scene # Assume unique rp names rplist_index = wrd.arm_rplist_index for i in range(0, len(wrd.arm_rplist)): if wrd.arm_rplist[i].name == item.arm_project_rp: wrd.arm_rplist_index = i break assets.invalidate_shader_cache(None, None) assets.invalidate_enabled = False make.build(item.arm_project_target, is_export=True) make.compile() wrd.arm_rplist_index = rplist_index assets.invalidate_enabled = True return{'FINISHED'} class ArmoryPublishProjectButton(bpy.types.Operator): """Build project ready for publishing.""" bl_idname = 'arm.publish_project' bl_label = 'Publish' @classmethod def poll(cls, context): wrd = bpy.data.worlds['Arm'] return wrd.arm_exporterlist_index >= 0 and len(wrd.arm_exporterlist) > 0 def execute(self, context): # Compare version Blender and Armory (major, minor) if not arm.utils.compare_version_blender_arm(): self.report({'INFO'}, 'For Armory to work correctly, you need Blender 2.93 LTS.') if not arm.utils.check_saved(self): return {"CANCELLED"} if not arm.utils.check_sdkpath(self): return {"CANCELLED"} self.report({'INFO'}, 'Publishing project, check console for details.') arm.utils.check_projectpath(self) arm.utils.check_default_props() wrd = bpy.data.worlds['Arm'] item = wrd.arm_exporterlist[wrd.arm_exporterlist_index] if item.arm_project_rp == '': item.arm_project_rp = wrd.arm_rplist[wrd.arm_rplist_index].name if item.arm_project_scene == None: item.arm_project_scene = context.scene # Assume unique rp names rplist_index = wrd.arm_rplist_index for i in range(0, len(wrd.arm_rplist)): if wrd.arm_rplist[i].name == item.arm_project_rp: wrd.arm_rplist_index = i break make.clean() assets.invalidate_enabled = False make.build(item.arm_project_target, is_publish=True, is_export=True) make.compile() wrd.arm_rplist_index = rplist_index assets.invalidate_enabled = True return{'FINISHED'} class ArmoryOpenProjectFolderButton(bpy.types.Operator): '''Open project folder''' bl_idname = 'arm.open_project_folder' bl_label = 'Project Folder' def execute(self, context): if not arm.utils.check_saved(self): return {"CANCELLED"} arm.utils.open_folder(arm.utils.get_fp()) return{'FINISHED'} class ArmoryOpenEditorButton(bpy.types.Operator): '''Launch this project in the IDE''' bl_idname = 'arm.open_editor' bl_label = 'Code Editor' bl_description = 'Open Project in IDE' def execute(self, context): if not arm.utils.check_saved(self): return {"CANCELLED"} 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') arm.utils.open_editor() return{'FINISHED'} class CleanMenu(bpy.types.Menu): bl_label = "Ok?" bl_idname = "OBJECT_MT_clean_menu" def draw(self, context): layout = self.layout layout.operator("arm.clean_project") class CleanButtonMenu(bpy.types.Operator): '''Clean cached data''' bl_label = "Clean" bl_idname = "arm.clean_menu" def execute(self, context): bpy.ops.wm.call_menu(name=CleanMenu.bl_idname) return {"FINISHED"} class ArmoryCleanProjectButton(bpy.types.Operator): '''Delete all cached project data''' bl_idname = 'arm.clean_project' bl_label = 'Clean Project' def execute(self, context): if not arm.utils.check_saved(self): return {"CANCELLED"} make.clean() return{'FINISHED'} def draw_view3d_header(self, context): if state.proc_build is not None: self.layout.label(text='Compiling..') elif log.info_text != '': self.layout.label(text=log.info_text) def draw_view3d_object_menu(self, context): self.layout.separator() self.layout.operator_context = 'INVOKE_DEFAULT' self.layout.operator('arm.copy_traits_to_active') class ARM_PT_RenderPathPanel(bpy.types.Panel): bl_label = "Armory Render Path" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "render" bl_options = {'DEFAULT_CLOSED'} def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] rows = 2 if len(wrd.arm_rplist) > 1: rows = 4 row = layout.row() row.template_list("ARM_UL_RPList", "The_List", wrd, "arm_rplist", wrd, "arm_rplist_index", rows=rows) col = row.column(align=True) col.operator("arm_rplist.new_item", icon='ADD', text="") col.operator("arm_rplist.delete_item", icon='REMOVE', text="") if len(wrd.arm_rplist) > 1: col.separator() op = col.operator("arm_rplist.move_item", icon='TRIA_UP', text="") op.direction = 'UP' op = col.operator("arm_rplist.move_item", icon='TRIA_DOWN', text="") op.direction = 'DOWN' if wrd.arm_rplist_index < 0 or len(wrd.arm_rplist) == 0: return rpdat = wrd.arm_rplist[wrd.arm_rplist_index] if len(arm.api.drivers) > 0: rpdat.rp_driver_list.clear() rpdat.rp_driver_list.add().name = 'Armory' for d in arm.api.drivers: rpdat.rp_driver_list.add().name = arm.api.drivers[d]['driver_name'] layout.prop_search(rpdat, "rp_driver", rpdat, "rp_driver_list", text="Driver") layout.separator() if rpdat.rp_driver != 'Armory' and arm.api.drivers[rpdat.rp_driver]['draw_props'] != None: arm.api.drivers[rpdat.rp_driver]['draw_props'](layout) return class ARM_PT_RenderPathRendererPanel(bpy.types.Panel): bl_label = "Renderer" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "render" bl_options = {'DEFAULT_CLOSED'} bl_parent_id = "ARM_PT_RenderPathPanel" def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] if len(wrd.arm_rplist) <= wrd.arm_rplist_index: return rpdat = wrd.arm_rplist[wrd.arm_rplist_index] layout.prop(rpdat, 'rp_renderer') if rpdat.rp_renderer == 'Forward': layout.prop(rpdat, 'rp_depthprepass') layout.prop(rpdat, 'arm_material_model') layout.prop(rpdat, 'rp_translucency_state') layout.prop(rpdat, 'rp_overlays_state') layout.prop(rpdat, 'rp_decals_state') layout.prop(rpdat, 'rp_blending_state') layout.prop(rpdat, 'rp_draw_order') layout.prop(rpdat, 'arm_samples_per_pixel') layout.prop(rpdat, 'arm_texture_filter') layout.prop(rpdat, 'rp_sss_state') col = layout.column() col.enabled = rpdat.rp_sss_state != 'Off' col.prop(rpdat, 'arm_sss_width') layout.prop(rpdat, 'arm_rp_displacement') if rpdat.arm_rp_displacement == 'Tessellation': layout.label(text='Mesh') layout.prop(rpdat, 'arm_tess_mesh_inner') layout.prop(rpdat, 'arm_tess_mesh_outer') layout.label(text='Shadow') layout.prop(rpdat, 'arm_tess_shadows_inner') layout.prop(rpdat, 'arm_tess_shadows_outer') layout.prop(rpdat, 'arm_particles') layout.separator(factor=0.1) col = layout.column() col.prop(rpdat, 'arm_skin') col = col.column() col.enabled = rpdat.arm_skin == 'On' col.prop(rpdat, 'arm_skin_max_bones_auto') row = col.row() row.enabled = not rpdat.arm_skin_max_bones_auto row.prop(rpdat, 'arm_skin_max_bones') layout.separator(factor=0.1) col = layout.column() col.prop(rpdat, "rp_hdr") col.prop(rpdat, "rp_stereo") col.prop(rpdat, 'arm_culling') col.prop(rpdat, 'rp_pp') class ARM_PT_RenderPathShadowsPanel(bpy.types.Panel): bl_label = "Shadows" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "render" bl_options = {'DEFAULT_CLOSED'} bl_parent_id = "ARM_PT_RenderPathPanel" def draw_header(self, context): wrd = bpy.data.worlds['Arm'] if len(wrd.arm_rplist) <= wrd.arm_rplist_index: return rpdat = wrd.arm_rplist[wrd.arm_rplist_index] self.layout.prop(rpdat, "rp_shadows", text="") def compute_subdivs(self, max, subdivs): l = [max] for i in range(subdivs - 1): l.append(int(max / 2)) max = max / 2 return l def tiles_per_light_type(self, rpdat: arm.props_renderpath.ArmRPListItem, light_type: str) -> int: if light_type == 'point': return 6 elif light_type == 'spot': return 1 else: return int(rpdat.rp_shadowmap_cascades) def lights_number_atlas(self, rpdat: arm.props_renderpath.ArmRPListItem, atlas_size: int, shadowmap_size: int, light_type: str) -> int: '''Compute number lights that could fit in an atlas''' lights = atlas_size / shadowmap_size lights *= lights / self.tiles_per_light_type(rpdat, light_type) return int(lights) def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] if len(wrd.arm_rplist) <= wrd.arm_rplist_index: return rpdat = wrd.arm_rplist[wrd.arm_rplist_index] layout.enabled = rpdat.rp_shadows col = layout.column() col.enabled = not rpdat.rp_shadowmap_atlas_single_map or not rpdat.rp_shadowmap_atlas col.prop(rpdat, 'rp_shadowmap_cube') layout.prop(rpdat, 'rp_shadowmap_cascade') layout.prop(rpdat, 'rp_shadowmap_cascades') col = layout.column() col2 = col.column() col2.enabled = rpdat.rp_shadowmap_cascades != '1' col2.prop(rpdat, 'arm_shadowmap_split') col.prop(rpdat, 'arm_shadowmap_bounds') col.prop(rpdat, 'arm_pcfsize') layout.separator() layout.prop(rpdat, 'rp_shadowmap_atlas') colatlas = layout.column() colatlas.enabled = rpdat.rp_shadowmap_atlas colatlas.prop(rpdat, 'rp_max_lights') colatlas.prop(rpdat, 'rp_max_lights_cluster') colatlas.prop(rpdat, 'rp_shadowmap_atlas_lod') colatlas_lod = colatlas.column() colatlas_lod.enabled = rpdat.rp_shadowmap_atlas_lod colatlas_lod.prop(rpdat, 'rp_shadowmap_atlas_lod_subdivisions') colatlas_lod_info = colatlas_lod.row() colatlas_lod_info.alignment = 'RIGHT' subdivs_list = self.compute_subdivs(int(rpdat.rp_shadowmap_cascade), int(rpdat.rp_shadowmap_atlas_lod_subdivisions)) subdiv_text = "Subdivisions for spot lights: " + ', '.join(map(str, subdivs_list)) colatlas_lod_info.label(text=subdiv_text, icon="IMAGE_ZDEPTH") if not rpdat.rp_shadowmap_atlas_single_map: colatlas_lod_info = colatlas_lod.row() colatlas_lod_info.alignment = 'RIGHT' subdivs_list = self.compute_subdivs(int(rpdat.rp_shadowmap_cube), int(rpdat.rp_shadowmap_atlas_lod_subdivisions)) subdiv_text = "Subdivisions for point lights: " + ', '.join(map(str, subdivs_list)) colatlas_lod_info.label(text=subdiv_text, icon="IMAGE_ZDEPTH") size_warning = int(rpdat.rp_shadowmap_cascade) > 2048 or int(rpdat.rp_shadowmap_cube) > 2048 colatlas.prop(rpdat, 'rp_shadowmap_atlas_single_map') # show size for single texture if rpdat.rp_shadowmap_atlas_single_map: colatlas_single = colatlas.column() colatlas_single.prop(rpdat, 'rp_shadowmap_atlas_max_size') if rpdat.rp_shadowmap_atlas_max_size != '': atlas_size = int(rpdat.rp_shadowmap_atlas_max_size) shadowmap_size = int(rpdat.rp_shadowmap_cascade) if shadowmap_size > 2048: size_warning = True point_lights = self.lights_number_atlas(rpdat, atlas_size, shadowmap_size, 'point') spot_lights = self.lights_number_atlas(rpdat, atlas_size, shadowmap_size, 'spot') dir_lights = self.lights_number_atlas(rpdat, atlas_size, shadowmap_size, 'sun') col = colatlas_single.row() col.alignment = 'RIGHT' col.label(text=f'Enough space for { point_lights } point lights or { spot_lights } spot lights or { dir_lights } directional lights.') else: # show size for all types colatlas_mixed = colatlas.column() colatlas_mixed.prop(rpdat, 'rp_shadowmap_atlas_max_size_spot') if rpdat.rp_shadowmap_atlas_max_size_spot != '': atlas_size = int(rpdat.rp_shadowmap_atlas_max_size_spot) shadowmap_size = int(rpdat.rp_shadowmap_cascade) spot_lights = self.lights_number_atlas(rpdat, atlas_size, shadowmap_size, 'spot') if shadowmap_size > 2048: size_warning = True col = colatlas_mixed.row() col.alignment = 'RIGHT' col.label(text=f'Enough space for {spot_lights} spot lights.') colatlas_mixed.prop(rpdat, 'rp_shadowmap_atlas_max_size_point') if rpdat.rp_shadowmap_atlas_max_size_point != '': atlas_size = int(rpdat.rp_shadowmap_atlas_max_size_point) shadowmap_size = int(rpdat.rp_shadowmap_cube) point_lights = self.lights_number_atlas(rpdat, atlas_size, shadowmap_size, 'point') if shadowmap_size > 2048: size_warning = True col = colatlas_mixed.row() col.alignment = 'RIGHT' col.label(text=f'Enough space for {point_lights} point lights.') colatlas_mixed.prop(rpdat, 'rp_shadowmap_atlas_max_size_sun') if rpdat.rp_shadowmap_atlas_max_size_sun != '': atlas_size = int(rpdat.rp_shadowmap_atlas_max_size_sun) shadowmap_size = int(rpdat.rp_shadowmap_cascade) dir_lights = self.lights_number_atlas(rpdat, atlas_size, shadowmap_size, 'sun') if shadowmap_size > 2048: size_warning = True col = colatlas_mixed.row() col.alignment = 'RIGHT' col.label(text=f'Enough space for {dir_lights} directional lights.') # show warning when user picks a size higher than 2048 (arbitrary number). if size_warning: col = layout.column() row = col.row() row.alignment = 'RIGHT' row.label(text='Warning: Game will crash if texture size is higher than max texture size allowed by target.', icon='ERROR') class ARM_PT_RenderPathVoxelsPanel(bpy.types.Panel): bl_label = "Voxel AO" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "render" bl_options = {'DEFAULT_CLOSED'} bl_parent_id = "ARM_PT_RenderPathPanel" def draw_header(self, context): wrd = bpy.data.worlds['Arm'] if len(wrd.arm_rplist) <= wrd.arm_rplist_index: return rpdat = wrd.arm_rplist[wrd.arm_rplist_index] self.layout.prop(rpdat, "rp_voxelao", text="") def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] if len(wrd.arm_rplist) <= wrd.arm_rplist_index: return rpdat = wrd.arm_rplist[wrd.arm_rplist_index] layout.enabled = rpdat.rp_voxelao layout.prop(rpdat, 'arm_voxelgi_shadows') layout.prop(rpdat, 'arm_voxelgi_cones') layout.prop(rpdat, 'rp_voxelgi_resolution') layout.prop(rpdat, 'rp_voxelgi_resolution_z') layout.prop(rpdat, 'arm_voxelgi_dimensions') layout.prop(rpdat, 'arm_voxelgi_revoxelize') col2 = layout.column() col2.enabled = rpdat.arm_voxelgi_revoxelize col2.prop(rpdat, 'arm_voxelgi_camera') col2.prop(rpdat, 'arm_voxelgi_temporal') layout.prop(rpdat, 'arm_voxelgi_occ') layout.prop(rpdat, 'arm_voxelgi_step') layout.prop(rpdat, 'arm_voxelgi_range') layout.prop(rpdat, 'arm_voxelgi_offset') layout.prop(rpdat, 'arm_voxelgi_aperture') class ARM_PT_RenderPathWorldPanel(bpy.types.Panel): bl_label = "World" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "render" bl_options = {'DEFAULT_CLOSED'} bl_parent_id = "ARM_PT_RenderPathPanel" def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] if len(wrd.arm_rplist) <= wrd.arm_rplist_index: return rpdat = wrd.arm_rplist[wrd.arm_rplist_index] layout.prop(rpdat, "rp_background") col = layout.column() col.prop(rpdat, 'arm_irradiance') colb = col.column() colb.enabled = rpdat.arm_irradiance colb.prop(rpdat, 'arm_radiance') sub = colb.row() sub.enabled = rpdat.arm_radiance sub.prop(rpdat, 'arm_radiance_size') layout.separator() layout.prop(rpdat, 'arm_clouds') col = layout.column(align=True) col.prop(rpdat, "rp_water") col = col.column(align=True) col.enabled = rpdat.rp_water col.prop(rpdat, 'arm_water_level') col.prop(rpdat, 'arm_water_density') col.prop(rpdat, 'arm_water_displace') col.prop(rpdat, 'arm_water_speed') col.prop(rpdat, 'arm_water_freq') col.prop(rpdat, 'arm_water_refract') col.prop(rpdat, 'arm_water_reflect') col.prop(rpdat, 'arm_water_color') class ARM_PT_RenderPathPostProcessPanel(bpy.types.Panel): bl_label = "Post Process" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "render" bl_options = {'DEFAULT_CLOSED'} bl_parent_id = "ARM_PT_RenderPathPanel" def draw_header(self, context): wrd = bpy.data.worlds['Arm'] if len(wrd.arm_rplist) <= wrd.arm_rplist_index: return rpdat = wrd.arm_rplist[wrd.arm_rplist_index] self.layout.prop(rpdat, "rp_render_to_texture", text="") def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] if len(wrd.arm_rplist) <= wrd.arm_rplist_index: return rpdat = wrd.arm_rplist[wrd.arm_rplist_index] layout.enabled = rpdat.rp_render_to_texture col = layout.column() col.prop(rpdat, "rp_antialiasing") col.prop(rpdat, "rp_supersampling") col = layout.column() col.prop(rpdat, 'arm_rp_resolution') if rpdat.arm_rp_resolution == 'Custom': col.prop(rpdat, 'arm_rp_resolution_size') col.prop(rpdat, 'arm_rp_resolution_filter') col.prop(rpdat, 'rp_dynres') layout.separator() col = layout.column() col.prop(rpdat, "rp_ssgi") sub = col.column() sub.enabled = rpdat.rp_ssgi != 'Off' sub.prop(rpdat, 'arm_ssgi_half_res') sub.prop(rpdat, 'arm_ssgi_rays') sub.prop(rpdat, 'arm_ssgi_radius') sub.prop(rpdat, 'arm_ssgi_strength') sub.prop(rpdat, 'arm_ssgi_max_steps') layout.separator(factor=0.5) layout.prop(rpdat, 'arm_micro_shadowing') layout.separator() col = layout.column() col.prop(rpdat, "rp_ssr") col = col.column() col.enabled = rpdat.rp_ssr col.prop(rpdat, 'arm_ssr_half_res') col.prop(rpdat, 'arm_ssr_ray_step') col.prop(rpdat, 'arm_ssr_min_ray_step') col.prop(rpdat, 'arm_ssr_search_dist') col.prop(rpdat, 'arm_ssr_falloff_exp') col.prop(rpdat, 'arm_ssr_jitter') layout.separator() col = layout.column() col.prop(rpdat, 'arm_ssrs') col = col.column() col.enabled = rpdat.arm_ssrs col.prop(rpdat, 'arm_ssrs_ray_step') layout.separator() col = layout.column() col.prop(rpdat, "rp_bloom") col = col.column() col.enabled = rpdat.rp_bloom col.prop(rpdat, 'arm_bloom_threshold') col.prop(rpdat, 'arm_bloom_strength') col.prop(rpdat, 'arm_bloom_radius') layout.separator() col = layout.column() col.prop(rpdat, "rp_motionblur") col = col.column() col.enabled = rpdat.rp_motionblur != 'Off' col.prop(rpdat, 'arm_motion_blur_intensity') layout.separator() col = layout.column() col.prop(rpdat, "rp_volumetriclight") col = col.column() col.enabled = rpdat.rp_volumetriclight col.prop(rpdat, 'arm_volumetric_light_air_color') col.prop(rpdat, 'arm_volumetric_light_air_turbidity') col.prop(rpdat, 'arm_volumetric_light_steps') layout.separator() col = layout.column() col.prop(rpdat, "rp_chromatic_aberration") col = col.column() col.enabled = rpdat.rp_chromatic_aberration col.prop(rpdat, 'arm_chromatic_aberration_type') col.prop(rpdat, 'arm_chromatic_aberration_strength') if rpdat.arm_chromatic_aberration_type == "Spectral": col.prop(rpdat, 'arm_chromatic_aberration_samples') class ARM_PT_RenderPathCompositorPanel(bpy.types.Panel): bl_label = "Compositor" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "render" bl_options = {'DEFAULT_CLOSED'} bl_parent_id = "ARM_PT_RenderPathPanel" def draw_header(self, context): wrd = bpy.data.worlds['Arm'] if len(wrd.arm_rplist) <= wrd.arm_rplist_index: return rpdat = wrd.arm_rplist[wrd.arm_rplist_index] self.layout.prop(rpdat, "rp_compositornodes", text="") def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] if len(wrd.arm_rplist) <= wrd.arm_rplist_index: return rpdat = wrd.arm_rplist[wrd.arm_rplist_index] layout.enabled = rpdat.rp_compositornodes layout.prop(rpdat, 'arm_tonemap') layout.separator() col = layout.column() draw_conditional_prop(col, 'Letterbox', rpdat, 'arm_letterbox', 'arm_letterbox_size') draw_conditional_prop(col, 'Sharpen', rpdat, 'arm_sharpen', 'arm_sharpen_strength') draw_conditional_prop(col, 'Vignette', rpdat, 'arm_vignette', 'arm_vignette_strength') draw_conditional_prop(col, 'Film Grain', rpdat, 'arm_grain', 'arm_grain_strength') layout.separator() col = layout.column() col.prop(rpdat, 'arm_fog') col = col.column(align=True) col.enabled = rpdat.arm_fog col.prop(rpdat, 'arm_fog_color') col.prop(rpdat, 'arm_fog_amounta') col.prop(rpdat, 'arm_fog_amountb') layout.separator() col = layout.column() col.prop(rpdat, "rp_autoexposure") sub = col.column(align=True) sub.enabled = rpdat.rp_autoexposure sub.prop(rpdat, 'arm_autoexposure_strength', text='Strength') sub.prop(rpdat, 'arm_autoexposure_speed', text='Speed') layout.separator() col = layout.column() col.prop(rpdat, 'arm_lensflare') col.prop(rpdat, 'arm_fisheye') layout.separator() col = layout.column() col.prop(rpdat, 'arm_lens_texture') if rpdat.arm_lens_texture != "": col.prop(rpdat, 'arm_lens_texture_masking') if rpdat.arm_lens_texture_masking: sub = col.column(align=True) sub.prop(rpdat, 'arm_lens_texture_masking_centerMinClip') sub.prop(rpdat, 'arm_lens_texture_masking_centerMaxClip') sub = col.column(align=True) sub.prop(rpdat, 'arm_lens_texture_masking_luminanceMin') sub.prop(rpdat, 'arm_lens_texture_masking_luminanceMax') col.prop(rpdat, 'arm_lens_texture_masking_brightnessExp') layout.separator() layout.prop(rpdat, 'arm_lut_texture') class ARM_PT_BakePanel(bpy.types.Panel): bl_label = "Armory Bake" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "render" bl_options = {'DEFAULT_CLOSED'} def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False scn = bpy.data.scenes[context.scene.name] row = layout.row(align=True) row.prop(scn, "arm_bakemode", expand=True) if scn.arm_bakemode == "Static Map": row = layout.row(align=True) row.alignment = 'EXPAND' row.operator("arm.bake_textures", icon="RENDER_STILL") row.operator("arm.bake_apply") col = layout.column() col.prop(scn, 'arm_bakelist_scale') col.prop(scn.cycles, "samples") layout.prop(scn, 'arm_bakelist_unwrap') rows = 2 if len(scn.arm_bakelist) > 1: rows = 4 row = layout.row() row.template_list("ARM_UL_BakeList", "The_List", scn, "arm_bakelist", scn, "arm_bakelist_index", rows=rows) col = row.column(align=True) col.operator("arm_bakelist.new_item", icon='ADD', text="") col.operator("arm_bakelist.delete_item", icon='REMOVE', text="") col.menu("ARM_MT_BakeListSpecials", icon='DOWNARROW_HLT', text="") if len(scn.arm_bakelist) > 1: col.separator() op = col.operator("arm_bakelist.move_item", icon='TRIA_UP', text="") op.direction = 'UP' op = col.operator("arm_bakelist.move_item", icon='TRIA_DOWN', text="") op.direction = 'DOWN' if scn.arm_bakelist_index >= 0 and len(scn.arm_bakelist) > 0: item = scn.arm_bakelist[scn.arm_bakelist_index] layout.prop_search(item, "obj", bpy.data, "objects", text="Object") layout.prop(item, "res_x") layout.prop(item, "res_y") class ArmGenLodButton(bpy.types.Operator): """Automatically generate LoD levels.""" bl_idname = 'arm.generate_lod' bl_label = 'Auto Generate' def lod_name(self, name, level): return name + '_LOD' + str(level + 1) def execute(self, context): obj = context.object if obj == None: return{'CANCELLED'} # Clear mdata = context.object.data mdata.arm_lodlist_index = 0 mdata.arm_lodlist.clear() # Lod levels wrd = bpy.data.worlds['Arm'] ratio = wrd.arm_lod_gen_ratio num_levels = wrd.arm_lod_gen_levels for level in range(0, num_levels): new_obj = obj.copy() for i in range(0, 3): new_obj.location[i] = 0 new_obj.rotation_euler[i] = 0 new_obj.scale[i] = 1 new_obj.data = obj.data.copy() new_obj.name = self.lod_name(obj.name, level) new_obj.parent = obj new_obj.hide_viewport = True new_obj.hide_render = True mod = new_obj.modifiers.new('Decimate', 'DECIMATE') mod.ratio = ratio ratio *= wrd.arm_lod_gen_ratio context.scene.collection.objects.link(new_obj) # Screen sizes for level in range(0, num_levels): mdata.arm_lodlist.add() mdata.arm_lodlist[-1].name = self.lod_name(obj.name, level) mdata.arm_lodlist[-1].screen_size_prop = (1 - (1 / (num_levels + 1)) * level) - (1 / (num_levels + 1)) return{'FINISHED'} class ARM_PT_LodPanel(bpy.types.Panel): bl_label = "Armory Lod" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "object" bl_options = {'DEFAULT_CLOSED'} def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False obj = bpy.context.object # Mesh only for now if obj.type != 'MESH': return mdata = obj.data rows = 2 if len(mdata.arm_lodlist) > 1: rows = 4 row = layout.row() row.template_list("ARM_UL_LodList", "The_List", mdata, "arm_lodlist", mdata, "arm_lodlist_index", rows=rows) col = row.column(align=True) col.operator("arm_lodlist.new_item", icon='ADD', text="") col.operator("arm_lodlist.delete_item", icon='REMOVE', text="") if len(mdata.arm_lodlist) > 1: col.separator() op = col.operator("arm_lodlist.move_item", icon='TRIA_UP', text="") op.direction = 'UP' op = col.operator("arm_lodlist.move_item", icon='TRIA_DOWN', text="") op.direction = 'DOWN' if mdata.arm_lodlist_index >= 0 and len(mdata.arm_lodlist) > 0: item = mdata.arm_lodlist[mdata.arm_lodlist_index] layout.prop_search(item, "name", bpy.data, "objects", text="Object") layout.prop(item, "screen_size_prop") layout.prop(mdata, "arm_lod_material") # Auto lod for meshes if obj.type == 'MESH': layout.separator() layout.operator("arm.generate_lod") wrd = bpy.data.worlds['Arm'] layout.prop(wrd, 'arm_lod_gen_levels') layout.prop(wrd, 'arm_lod_gen_ratio') class ArmGenTerrainButton(bpy.types.Operator): '''Generate terrain sectors''' bl_idname = 'arm.generate_terrain' bl_label = 'Generate' def execute(self, context): scn = context.scene if scn == None: return{'CANCELLED'} sectors = scn.arm_terrain_sectors size = scn.arm_terrain_sector_size height_scale = scn.arm_terrain_height_scale # Create material mat = bpy.data.materials.new(name="Terrain") mat.use_nodes = True nodes = mat.node_tree.nodes links = mat.node_tree.links node = nodes.new('ShaderNodeDisplacement') node.location = (-200, 100) node.inputs[2].default_value = height_scale node.space = 'WORLD' links.new(nodes['Material Output'].inputs[2], node.outputs[0]) node = nodes.new('ShaderNodeTexImage') node.location = (-600, 100) node.interpolation = 'Closest' node.extension = 'EXTEND' node.arm_material_param = True node.name = '_TerrainHeight' node.label = '_TerrainHeight' # Height-map texture link for this sector links.new(nodes['Displacement'].inputs[0], nodes['_TerrainHeight'].outputs[0]) node = nodes.new('ShaderNodeBump') node.location = (-200, -200) node.inputs[0].default_value = 5.0 links.new(nodes['Bump'].inputs[2], nodes['_TerrainHeight'].outputs[0]) links.new(nodes['Principled BSDF'].inputs[20], nodes['Bump'].outputs[0]) # Create sectors root_obj = bpy.data.objects.new("Terrain", None) root_obj.location[0] = 0 root_obj.location[1] = 0 root_obj.location[2] = 0 root_obj.arm_export = False scn.collection.objects.link(root_obj) scn.arm_terrain_object = root_obj for i in range(sectors[0] * sectors[1]): j = str(i + 1).zfill(2) x = i % sectors[0] y = int(i / sectors[0]) bpy.ops.mesh.primitive_plane_add(location=(x * size, -y * size, 0)) slice_obj = bpy.context.active_object slice_obj.scale[0] = size / 2 slice_obj.scale[1] = -(size / 2) slice_obj.scale[2] = height_scale slice_obj.data.materials.append(mat) for p in slice_obj.data.polygons: p.use_smooth = True slice_obj.name = 'Terrain.' + j slice_obj.parent = root_obj sub_mod = slice_obj.modifiers.new('Subdivision', 'SUBSURF') sub_mod.subdivision_type = 'SIMPLE' disp_mod = slice_obj.modifiers.new('Displace', 'DISPLACE') disp_mod.texture_coords = 'UV' disp_mod.texture = bpy.data.textures.new(name='Terrain.' + j, type='IMAGE') disp_mod.texture.extension = 'EXTEND' disp_mod.texture.use_interpolation = False disp_mod.texture.use_mipmap = False disp_mod.texture.image = bpy.data.images.load(filepath=scn.arm_terrain_textures+'/heightmap_' + j + '.png') f = 1 levels = 0 while f < disp_mod.texture.image.size[0]: f *= 2 levels += 1 sub_mod.levels = sub_mod.render_levels = levels return{'FINISHED'} class ARM_PT_TerrainPanel(bpy.types.Panel): bl_label = "Armory Terrain" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" bl_options = {'DEFAULT_CLOSED'} def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False scn = bpy.context.scene if scn == None: return layout.prop(scn, 'arm_terrain_textures') layout.prop(scn, 'arm_terrain_sectors') layout.prop(scn, 'arm_terrain_sector_size') layout.prop(scn, 'arm_terrain_height_scale') layout.operator('arm.generate_terrain') layout.prop(scn, 'arm_terrain_object') class ARM_PT_TilesheetPanel(bpy.types.Panel): bl_label = "Armory Tilesheet" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "material" bl_options = {'DEFAULT_CLOSED'} def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] rows = 2 if len(wrd.arm_tilesheetlist) > 1: rows = 4 row = layout.row() row.template_list("ARM_UL_TilesheetList", "The_List", wrd, "arm_tilesheetlist", wrd, "arm_tilesheetlist_index", rows=rows) col = row.column(align=True) col.operator("arm_tilesheetlist.new_item", icon='ADD', text="") col.operator("arm_tilesheetlist.delete_item", icon='REMOVE', text="") if len(wrd.arm_tilesheetlist) > 1: col.separator() op = col.operator("arm_tilesheetlist.move_item", icon='TRIA_UP', text="") op.direction = 'UP' op = col.operator("arm_tilesheetlist.move_item", icon='TRIA_DOWN', text="") op.direction = 'DOWN' if wrd.arm_tilesheetlist_index >= 0 and len(wrd.arm_tilesheetlist) > 0: dat = wrd.arm_tilesheetlist[wrd.arm_tilesheetlist_index] layout.prop(dat, "tilesx_prop") layout.prop(dat, "tilesy_prop") layout.prop(dat, "framerate_prop") layout.label(text='Actions') rows = 2 if len(dat.arm_tilesheetactionlist) > 1: rows = 4 row = layout.row() row.template_list("ARM_UL_TilesheetList", "The_List", dat, "arm_tilesheetactionlist", dat, "arm_tilesheetactionlist_index", rows=rows) col = row.column(align=True) col.operator("arm_tilesheetactionlist.new_item", icon='ADD', text="") col.operator("arm_tilesheetactionlist.delete_item", icon='REMOVE', text="") if len(dat.arm_tilesheetactionlist) > 1: col.separator() op = col.operator("arm_tilesheetactionlist.move_item", icon='TRIA_UP', text="") op.direction = 'UP' op = col.operator("arm_tilesheetactionlist.move_item", icon='TRIA_DOWN', text="") op.direction = 'DOWN' if dat.arm_tilesheetactionlist_index >= 0 and len(dat.arm_tilesheetactionlist) > 0: adat = dat.arm_tilesheetactionlist[dat.arm_tilesheetactionlist_index] layout.prop(adat, "start_prop") layout.prop(adat, "end_prop") layout.prop(adat, "loop_prop") class ARM_PT_ProxyPanel(bpy.types.Panel): bl_label = "Armory Proxy" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "object" bl_options = {'DEFAULT_CLOSED'} def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False layout.operator("arm.make_proxy") obj = bpy.context.object if obj is not None and obj.proxy is not None: col = layout.column(heading="Sync") col.prop(obj, "arm_proxy_sync_loc") col.prop(obj, "arm_proxy_sync_rot") col.prop(obj, "arm_proxy_sync_scale") col.separator() col.prop(obj, "arm_proxy_sync_materials") col.prop(obj, "arm_proxy_sync_modifiers") col.separator() col.prop(obj, "arm_proxy_sync_traits") row = col.row() row.enabled = obj.arm_proxy_sync_traits row.prop(obj, "arm_proxy_sync_trait_props") row = layout.row(align=True) row.operator("arm.proxy_toggle_all") row.operator("arm.proxy_apply_all") class ArmMakeProxyButton(bpy.types.Operator): '''Create proxy from linked object''' bl_idname = 'arm.make_proxy' bl_label = 'Make Proxy' def execute(self, context): obj = context.object if obj == None: return{'CANCELLED'} if obj.library == None: self.report({'ERROR'}, 'Select linked object') arm.proxy.make(obj) return{'FINISHED'} class ArmProxyToggleAllButton(bpy.types.Operator): bl_idname = 'arm.proxy_toggle_all' bl_label = 'Toggle All' def execute(self, context): obj = context.object b = not obj.arm_proxy_sync_loc obj.arm_proxy_sync_loc = b obj.arm_proxy_sync_rot = b obj.arm_proxy_sync_scale = b obj.arm_proxy_sync_materials = b obj.arm_proxy_sync_modifiers = b obj.arm_proxy_sync_traits = b obj.arm_proxy_sync_trait_props = b return{'FINISHED'} class ArmProxyApplyAllButton(bpy.types.Operator): bl_idname = 'arm.proxy_apply_all' bl_label = 'Apply to All' def execute(self, context): for obj in bpy.data.objects: if obj.proxy == None: continue if obj.proxy == context.object.proxy: obj.arm_proxy_sync_loc = context.object.arm_proxy_sync_loc obj.arm_proxy_sync_rot = context.object.arm_proxy_sync_rot obj.arm_proxy_sync_scale = context.object.arm_proxy_sync_scale obj.arm_proxy_sync_materials = context.object.arm_proxy_sync_materials obj.arm_proxy_sync_modifiers = context.object.arm_proxy_sync_modifiers obj.arm_proxy_sync_traits = context.object.arm_proxy_sync_traits obj.arm_proxy_sync_trait_props = context.object.arm_proxy_sync_trait_props return{'FINISHED'} class ArmSyncProxyButton(bpy.types.Operator): bl_idname = 'arm.sync_proxy' bl_label = 'Sync' def execute(self, context): if len(bpy.data.libraries) > 0: for obj in bpy.data.objects: if obj == None or obj.proxy == None: continue if obj.arm_proxy_sync_loc: arm.proxy.sync_location(obj) if obj.arm_proxy_sync_rot: arm.proxy.sync_rotation(obj) if obj.arm_proxy_sync_scale: arm.proxy.sync_scale(obj) if obj.arm_proxy_sync_materials: arm.proxy.sync_materials(obj) if obj.arm_proxy_sync_modifiers: arm.proxy.sync_modifiers(obj) if obj.arm_proxy_sync_traits: arm.proxy.sync_traits(obj) print('Armory: Proxy objects synchronized') return{'FINISHED'} class ArmPrintTraitsButton(bpy.types.Operator): bl_idname = 'arm.print_traits' bl_label = 'Print Traits' def execute(self, context): for s in bpy.data.scenes: print(s.name + ' traits:') for o in s.objects: for t in o.arm_traitlist: if not t.enabled_prop: continue tname = t.node_tree_prop.name if t.type_prop == 'Logic Nodes' else t.class_name_prop print('Object {0} - {1}'.format(o.name, tname)) return{'FINISHED'} class ARM_PT_MaterialNodePanel(bpy.types.Panel): bl_label = 'Armory Material Node' bl_idname = 'ARM_PT_MaterialNodePanel' bl_space_type = 'NODE_EDITOR' bl_region_type = 'UI' bl_category = 'Armory' @classmethod def poll(cls, context): return (context.space_data.tree_type == 'ShaderNodeTree' and context.space_data.edit_tree and context.space_data.shader_type == 'OBJECT') def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False n = context.active_node if n != None and (n.bl_idname == 'ShaderNodeRGB' or n.bl_idname == 'ShaderNodeValue' or n.bl_idname == 'ShaderNodeTexImage'): layout.prop(context.active_node, 'arm_material_param') class ARM_OT_ShowFileVersionInfo(bpy.types.Operator): bl_label = 'Show old file version info' bl_idname = 'arm.show_old_file_version_info' bl_description = ('Displays an info panel that warns about opening a file' 'which was created in a previous version of Armory') bl_options = {'INTERNAL'} wrd = None def draw_message_box(self, context): file_version = ARM_OT_ShowFileVersionInfo.wrd.arm_version current_version = props.arm_version layout = self.layout layout = layout.column(align=True) layout.alignment = 'EXPAND' if current_version == file_version: layout.label('This file was saved in', icon='INFO') layout.label('the current Armory version', icon='BLANK1') layout.separator() layout.label(f'(version: {current_version}') row = layout.row(align=True) row.active_default = True row.operator('arm.discard_popup', text='Ok') # this will help order versions better, somewhat. # note: this is NOT complete current_version = tuple( current_version.split('.') ) file_version = tuple( file_version.split('.') ) if current_version > file_version: layout.label(text='Warning: This file was saved in a', icon='ERROR') layout.label(text='previous version of Armory!', icon='BLANK1') layout.separator() layout.label(text='Please inform yourself about breaking changes!', icon='BLANK1') layout.label(text=f'File saved in: {file_version}', icon='BLANK1') layout.label(text=f'Current version: {current_version}', icon='BLANK1') layout.separator() layout.separator() layout.label(text='Should Armory try to automatically update', icon='BLANK1') layout.label(text='the file to the current SDK version?', icon='BLANK1') layout.separator() row = layout.row(align=True) row.active_default = True row.operator('arm.update_file_sdk', text='Yes') row.active_default = False row.operator('arm.discard_popup', text='No') else: layout.label(text='Warning: This file was saved in a', icon='ERROR') layout.label(text='future version of Armory!', icon='BLANK1') layout.separator() layout.label(text='It is impossible to downgrade a file,', icon='BLANK1') layout.label(text='Something will probably be broken here.', icon='BLANK1') layout.label(text=f'File saved in: {file_version}', icon='BLANK1') layout.label(text=f'Current version: {current_version}', icon='BLANK1') layout.separator() layout.separator() layout.label(text='Please check how this file was created', icon='BLANK1') layout.separator() row = layout.row(align=True) row.active_default = True row.operator('arm.discard_popup', text='Ok') def execute(self, context): ARM_OT_ShowFileVersionInfo.wrd = bpy.data.worlds['Arm'] context.window_manager.popover(ARM_OT_ShowFileVersionInfo.draw_message_box, ui_units_x=16) return {"FINISHED"} class ARM_OT_ShowNodeUpdateErrors(bpy.types.Operator): bl_label = 'Show upgrade failure details' bl_idname = 'arm.show_node_update_errors' bl_description = ('Displays an info panel that shows the different errors that occurred when upgrading nodes') wrd = None # a helper internal variable def draw_message_box(self, context): list_of_errors = arm.logicnode.replacement.replacement_errors.copy() # note: list_of_errors is a set of tuples: `(error_type, node_class, tree_name)` # where `error_type` can be "unregistered", "update failed", "future version", "bad version", or "misc." file_version = ARM_OT_ShowNodeUpdateErrors.wrd.arm_version current_version = props.arm_version # this will help order versions better, somewhat. # note: this is NOT complete current_version_2 = tuple(current_version.split('.')) file_version_2 = tuple(file_version.split('.')) is_armory_upgrade = (current_version_2 > file_version_2) error_types = set() errored_trees = set() errored_nodes = set() for error_entry in list_of_errors: error_types.add(error_entry[0]) errored_nodes.add(error_entry[1]) errored_trees.add(error_entry[2]) layout = self.layout layout = layout.column(align=True) layout.alignment = 'EXPAND' layout.label(text="Some nodes failed to be updated to the current Armory version", icon="ERROR") if current_version == file_version: layout.label(text="(This might be because you are using a development snapshot, or a homemade version ;) )", icon='BLANK1') elif not is_armory_upgrade: layout.label(text="(Please note that it is not possible do downgrade nodes to a previous version either.", icon='BLANK1') layout.label(text="This might be the cause of your problem.)", icon='BLANK1') layout.label(text=f'File saved in: {file_version}', icon='BLANK1') layout.label(text=f'Current version: {current_version}', icon='BLANK1') layout.separator() if 'update failed' in error_types: layout.label(text="Some nodes do not have an update procedure to deal with the version saved in this file.", icon='BLANK1') if current_version == file_version: layout.label(text="(if you are a developer, this might be because you didn't implement it yet.)", icon='BLANK1') if 'bad version' in error_types: layout.label(text="Some nodes do not have version information attached to them.", icon='BLANK1') if 'unregistered' in error_types: if is_armory_upgrade: layout.label(text='Some nodes seem to be too old to be understood by armory anymore', icon='BLANK1') else: layout.label(text="Some nodes are unknown to armory, either because they are too new or too old.", icon='BLANK1') if 'future version' in error_types: if is_armory_upgrade: layout.label(text='Somehow, some nodes seem to have been created with a future version of armory.', icon='BLANK1') else: layout.label(text='Some nodes seem to have been created with a future version of armory.', icon='BLANK1') if 'misc.' in error_types: layout.label(text="Some nodes' update procedure failed to complete") layout.separator() layout.label(text='the nodes impacted are the following:', icon='BLANK1') for node in errored_nodes: layout.label(text=f' {node}', icon='BLANK1') layout.separator() layout.label(text='the node trees impacted are the following:', icon='BLANK1') for tree in errored_trees: layout.label(text=f' "{tree}"', icon='BLANK1') layout.separator() layout.label(text="A detailed error report has been saved next to the blender file.", icon='BLANK1') layout.label(text="the file name is \"node_update_failure\", followed by the current time.", icon='BLANK1') layout.separator() row = layout.row(align=True) row.active_default = False row.operator('arm.discard_popup', text='Ok') row.operator('arm.open_project_folder', text='Open Project Folder', icon="FILE_FOLDER") def execute(self, context): ARM_OT_ShowNodeUpdateErrors.wrd = bpy.data.worlds['Arm'] context.window_manager.popover(ARM_OT_ShowNodeUpdateErrors.draw_message_box, ui_units_x=32) return {"FINISHED"} class ARM_OT_UpdateFileSDK(bpy.types.Operator): bl_idname = 'arm.update_file_sdk' bl_label = 'Update file to current SDK version' bl_description = bl_label bl_options = {'INTERNAL'} def execute(self, context): wrd = bpy.data.worlds['Arm'] # This allows for seamless migration from ealier versions of Armory for rp in wrd.arm_rplist: # TODO: deprecated if rp.rp_gi != 'Off': rp.rp_gi = 'Off' rp.rp_voxelao = True # Replace deprecated nodes arm.logicnode.replacement.replace_all() wrd.arm_version = props.arm_version wrd.arm_commit = props.arm_commit arm.make.clean() print(f'Project updated to SDK {props.arm_version}. Please save the .blend file.') return {'FINISHED'} class ARM_OT_DiscardPopup(bpy.types.Operator): """Empty operator for discarding dialogs.""" bl_idname = 'arm.discard_popup' bl_label = 'OK' bl_description = 'Discard' bl_options = {'INTERNAL'} def execute(self, context): return {'FINISHED'} class ArmoryUpdateListAndroidEmulatorButton(bpy.types.Operator): '''Updating the list of emulators for the Android platform''' bl_idname = 'arm.update_list_android_emulator' bl_label = 'Update List Emulators' def execute(self, context): if not arm.utils.check_saved(self): return {"CANCELLED"} if not arm.utils.check_sdkpath(self): return {"CANCELLED"} if len(arm.utils.get_android_sdk_root_path()) == 0: return {"CANCELLED"} os.environ['ANDROID_SDK_ROOT'] = arm.utils.get_android_sdk_root_path() items, err = arm.utils.get_android_emulators_list() if len(err) > 0: print('Update List Emulators Warning: File "'+ arm.utils.get_android_emulator_file() +'" not found. Check that the variable ANDROID_SDK_ROOT is correct in environment variables or in "Android SDK Path" setting: \n- If you specify an environment variable ANDROID_SDK_ROOT, then you need to restart Blender;\n- If you specify the setting "Android SDK Path", then repeat operation "Publish"') return{'FINISHED'} if len(items) > 0: items_enum = [] for i in items: items_enum.append((i, i, i)) bpy.types.World.arm_project_android_list_avd = EnumProperty(items=items_enum, name="Emulator", update=assets.invalidate_compiler_cache) return{'FINISHED'} class ArmoryUpdateListAndroidEmulatorRunButton(bpy.types.Operator): '''Launch Android emulator selected from the list''' bl_idname = 'arm.run_android_emulator' bl_label = 'Launch Emulator' def execute(self, context): if not arm.utils.check_saved(self): return {"CANCELLED"} if not arm.utils.check_sdkpath(self): return {"CANCELLED"} if len(arm.utils.get_android_sdk_root_path()) == 0: return {"CANCELLED"} make.run_android_emulators(arm.utils.get_android_emulator_name()) return{'FINISHED'} class ArmoryUpdateListInstalledVSButton(bpy.types.Operator): '''Updating the list installed Visual Studio for the Windows platform''' bl_idname = 'arm.update_list_installed_vs' bl_label = 'Update List Installed Visual Studio' def execute(self, context): if not arm.utils.check_saved(self): return {"CANCELLED"} if not arm.utils.check_sdkpath(self): return {"CANCELLED"} if not arm.utils.get_os_is_windows(): return {"CANCELLED"} wrd = bpy.data.worlds['Arm'] items, err = arm.utils.get_list_installed_vs_version() if len(err) > 0: print('Warning for operation Update List Installed Visual Studio: '+ err +'. Check if ArmorySDK is installed correctly.') return{'FINISHED'} if len(items) > 0: items_enum = [('10', '2010', 'Visual Studio 2010 (version 10)'), ('11', '2012', 'Visual Studio 2012 (version 11)'), ('12', '2013', 'Visual Studio 2013 (version 12)'), ('14', '2015', 'Visual Studio 2015 (version 14)'), ('15', '2017', 'Visual Studio 2017 (version 15)'), ('16', '2019', 'Visual Studio 2019 (version 16)')] prev_select = wrd.arm_project_win_list_vs res_items_enum = [] for vs in items_enum: l_vs = list(vs) for ver in items: if l_vs[0] == ver[0]: l_vs[1] = l_vs[1] + ' (installed)' l_vs[2] = l_vs[2] + ' (installed)' break res_items_enum.append((l_vs[0], l_vs[1], l_vs[2])) bpy.types.World.arm_project_win_list_vs = EnumProperty(items=res_items_enum, name="Visual Studio Version", default=prev_select, update=assets.invalidate_compiler_cache) return{'FINISHED'} def draw_custom_node_menu(self, context): """Extension of the node context menu. https://blender.stackexchange.com/questions/150101/python-how-to-add-items-in-context-menu-in-2-8 """ if context.selected_nodes is None or len(context.selected_nodes) != 1: return if context.space_data.tree_type == 'ArmLogicTreeType': if context.selected_nodes[0].bl_idname.startswith('LN'): layout = self.layout layout.separator() layout.operator("arm.open_node_documentation", text="Show documentation for this node", icon='HELP') layout.operator("arm.open_node_haxe_source", text="Open .hx source in the browser", icon_value=ui_icons.get_id("haxe")) layout.operator("arm.open_node_python_source", text="Open .py source in the browser", icon='FILE_SCRIPT') elif context.space_data.tree_type == 'ShaderNodeTree': if context.active_node.bl_idname in ('ShaderNodeRGB', 'ShaderNodeValue', 'ShaderNodeTexImage'): layout = self.layout layout.separator() layout.prop(context.active_node, 'arm_material_param', text='Armory: Material Parameter') def draw_conditional_prop(layout: bpy.types.UILayout, heading: str, data: bpy.types.AnyType, prop_condition: str, prop_value: str) -> None: """Draws a property row with a checkbox that enables a value field. The function fails when prop_condition is not a boolean property. """ col = layout.column(heading=heading) row = col.row() row.prop(data, prop_condition, text='') sub = row.row() sub.enabled = getattr(data, prop_condition) sub.prop(data, prop_value, expand=True) def register(): bpy.utils.register_class(ARM_PT_ObjectPropsPanel) bpy.utils.register_class(ARM_PT_ModifiersPropsPanel) bpy.utils.register_class(ARM_PT_ParticlesPropsPanel) bpy.utils.register_class(ARM_PT_PhysicsPropsPanel) bpy.utils.register_class(ARM_PT_DataPropsPanel) bpy.utils.register_class(ARM_PT_ScenePropsPanel) bpy.utils.register_class(ARM_PT_WorldPropsPanel) bpy.utils.register_class(InvalidateCacheButton) bpy.utils.register_class(InvalidateMaterialCacheButton) bpy.utils.register_class(ARM_OT_NewCustomMaterial) bpy.utils.register_class(ARM_PT_MaterialPropsPanel) bpy.utils.register_class(ARM_PT_MaterialBlendingPropsPanel) bpy.utils.register_class(ARM_PT_MaterialDriverPropsPanel) bpy.utils.register_class(ARM_PT_ArmoryPlayerPanel) bpy.utils.register_class(ARM_PT_ArmoryExporterPanel) bpy.utils.register_class(ARM_PT_ArmoryExporterAndroidSettingsPanel) bpy.utils.register_class(ARM_PT_ArmoryExporterAndroidPermissionsPanel) bpy.utils.register_class(ARM_PT_ArmoryExporterAndroidAbiPanel) bpy.utils.register_class(ARM_PT_ArmoryExporterAndroidBuildAPKPanel) bpy.utils.register_class(ARM_PT_ArmoryExporterHTML5SettingsPanel) bpy.utils.register_class(ARM_PT_ArmoryExporterWindowsSettingsPanel) bpy.utils.register_class(ARM_PT_ArmoryProjectPanel) bpy.utils.register_class(ARM_PT_ProjectFlagsPanel) bpy.utils.register_class(ARM_PT_ProjectFlagsDebugConsolePanel) bpy.utils.register_class(ARM_PT_ProjectWindowPanel) bpy.utils.register_class(ARM_PT_ProjectModulesPanel) bpy.utils.register_class(ARM_PT_RenderPathPanel) bpy.utils.register_class(ARM_PT_RenderPathRendererPanel) bpy.utils.register_class(ARM_PT_RenderPathShadowsPanel) bpy.utils.register_class(ARM_PT_RenderPathVoxelsPanel) bpy.utils.register_class(ARM_PT_RenderPathWorldPanel) bpy.utils.register_class(ARM_PT_RenderPathPostProcessPanel) bpy.utils.register_class(ARM_PT_RenderPathCompositorPanel) bpy.utils.register_class(ARM_PT_BakePanel) # bpy.utils.register_class(ArmVirtualInputPanel) bpy.utils.register_class(ArmoryPlayButton) bpy.utils.register_class(ArmoryStopButton) bpy.utils.register_class(ArmoryBuildProjectButton) bpy.utils.register_class(ArmoryOpenProjectFolderButton) bpy.utils.register_class(ArmoryOpenEditorButton) bpy.utils.register_class(CleanMenu) bpy.utils.register_class(CleanButtonMenu) bpy.utils.register_class(ArmoryCleanProjectButton) bpy.utils.register_class(ArmoryPublishProjectButton) bpy.utils.register_class(ArmGenLodButton) bpy.utils.register_class(ARM_PT_LodPanel) bpy.utils.register_class(ArmGenTerrainButton) bpy.utils.register_class(ARM_PT_TerrainPanel) bpy.utils.register_class(ARM_PT_TilesheetPanel) bpy.utils.register_class(ARM_PT_ProxyPanel) bpy.utils.register_class(ArmMakeProxyButton) bpy.utils.register_class(ArmProxyToggleAllButton) bpy.utils.register_class(ArmProxyApplyAllButton) bpy.utils.register_class(ArmSyncProxyButton) bpy.utils.register_class(ArmPrintTraitsButton) bpy.utils.register_class(ARM_PT_MaterialNodePanel) bpy.utils.register_class(ARM_OT_UpdateFileSDK) bpy.utils.register_class(ARM_OT_ShowFileVersionInfo) bpy.utils.register_class(ARM_OT_ShowNodeUpdateErrors) bpy.utils.register_class(ARM_OT_DiscardPopup) bpy.utils.register_class(ArmoryUpdateListAndroidEmulatorButton) bpy.utils.register_class(ArmoryUpdateListAndroidEmulatorRunButton) bpy.utils.register_class(ArmoryUpdateListInstalledVSButton) bpy.utils.register_class(scene.TLM_PT_Settings) bpy.utils.register_class(scene.TLM_PT_Denoise) bpy.utils.register_class(scene.TLM_PT_Filtering) bpy.utils.register_class(scene.TLM_PT_Encoding) bpy.utils.register_class(scene.TLM_PT_Utility) bpy.utils.register_class(scene.TLM_PT_Additional) bpy.types.VIEW3D_HT_header.append(draw_view3d_header) bpy.types.VIEW3D_MT_object.append(draw_view3d_object_menu) bpy.types.NODE_MT_context_menu.append(draw_custom_node_menu) def unregister(): bpy.types.NODE_MT_context_menu.remove(draw_custom_node_menu) bpy.types.VIEW3D_MT_object.remove(draw_view3d_object_menu) bpy.types.VIEW3D_HT_header.remove(draw_view3d_header) bpy.utils.unregister_class(ArmoryUpdateListInstalledVSButton) bpy.utils.unregister_class(ArmoryUpdateListAndroidEmulatorRunButton) bpy.utils.unregister_class(ArmoryUpdateListAndroidEmulatorButton) bpy.utils.unregister_class(ARM_OT_DiscardPopup) bpy.utils.unregister_class(ARM_OT_ShowNodeUpdateErrors) bpy.utils.unregister_class(ARM_OT_ShowFileVersionInfo) bpy.utils.unregister_class(ARM_OT_UpdateFileSDK) bpy.utils.unregister_class(ARM_PT_ObjectPropsPanel) bpy.utils.unregister_class(ARM_PT_ModifiersPropsPanel) bpy.utils.unregister_class(ARM_PT_ParticlesPropsPanel) bpy.utils.unregister_class(ARM_PT_PhysicsPropsPanel) bpy.utils.unregister_class(ARM_PT_DataPropsPanel) bpy.utils.unregister_class(ARM_PT_WorldPropsPanel) bpy.utils.unregister_class(ARM_PT_ScenePropsPanel) bpy.utils.unregister_class(InvalidateCacheButton) bpy.utils.unregister_class(InvalidateMaterialCacheButton) bpy.utils.unregister_class(ARM_OT_NewCustomMaterial) bpy.utils.unregister_class(ARM_PT_MaterialDriverPropsPanel) bpy.utils.unregister_class(ARM_PT_MaterialBlendingPropsPanel) bpy.utils.unregister_class(ARM_PT_MaterialPropsPanel) bpy.utils.unregister_class(ARM_PT_ArmoryPlayerPanel) bpy.utils.unregister_class(ARM_PT_ArmoryExporterWindowsSettingsPanel) bpy.utils.unregister_class(ARM_PT_ArmoryExporterHTML5SettingsPanel) bpy.utils.unregister_class(ARM_PT_ArmoryExporterAndroidBuildAPKPanel) bpy.utils.unregister_class(ARM_PT_ArmoryExporterAndroidAbiPanel) bpy.utils.unregister_class(ARM_PT_ArmoryExporterAndroidPermissionsPanel) bpy.utils.unregister_class(ARM_PT_ArmoryExporterAndroidSettingsPanel) bpy.utils.unregister_class(ARM_PT_ArmoryExporterPanel) bpy.utils.unregister_class(ARM_PT_ArmoryProjectPanel) bpy.utils.unregister_class(ARM_PT_ProjectFlagsDebugConsolePanel) bpy.utils.unregister_class(ARM_PT_ProjectFlagsPanel) bpy.utils.unregister_class(ARM_PT_ProjectWindowPanel) bpy.utils.unregister_class(ARM_PT_ProjectModulesPanel) bpy.utils.unregister_class(ARM_PT_RenderPathPanel) bpy.utils.unregister_class(ARM_PT_RenderPathRendererPanel) bpy.utils.unregister_class(ARM_PT_RenderPathShadowsPanel) bpy.utils.unregister_class(ARM_PT_RenderPathVoxelsPanel) bpy.utils.unregister_class(ARM_PT_RenderPathWorldPanel) bpy.utils.unregister_class(ARM_PT_RenderPathPostProcessPanel) bpy.utils.unregister_class(ARM_PT_RenderPathCompositorPanel) bpy.utils.unregister_class(ARM_PT_BakePanel) # bpy.utils.unregister_class(ArmVirtualInputPanel) bpy.utils.unregister_class(ArmoryPlayButton) bpy.utils.unregister_class(ArmoryStopButton) bpy.utils.unregister_class(ArmoryBuildProjectButton) bpy.utils.unregister_class(ArmoryOpenProjectFolderButton) bpy.utils.unregister_class(ArmoryOpenEditorButton) bpy.utils.unregister_class(CleanMenu) bpy.utils.unregister_class(CleanButtonMenu) bpy.utils.unregister_class(ArmoryCleanProjectButton) bpy.utils.unregister_class(ArmoryPublishProjectButton) bpy.utils.unregister_class(ArmGenLodButton) bpy.utils.unregister_class(ARM_PT_LodPanel) bpy.utils.unregister_class(ArmGenTerrainButton) bpy.utils.unregister_class(ARM_PT_TerrainPanel) bpy.utils.unregister_class(ARM_PT_TilesheetPanel) bpy.utils.unregister_class(ARM_PT_ProxyPanel) bpy.utils.unregister_class(ArmMakeProxyButton) bpy.utils.unregister_class(ArmProxyToggleAllButton) bpy.utils.unregister_class(ArmProxyApplyAllButton) bpy.utils.unregister_class(ArmSyncProxyButton) bpy.utils.unregister_class(ArmPrintTraitsButton) bpy.utils.unregister_class(ARM_PT_MaterialNodePanel) bpy.utils.unregister_class(scene.TLM_PT_Settings) bpy.utils.unregister_class(scene.TLM_PT_Denoise) bpy.utils.unregister_class(scene.TLM_PT_Filtering) bpy.utils.unregister_class(scene.TLM_PT_Encoding) bpy.utils.unregister_class(scene.TLM_PT_Utility) bpy.utils.unregister_class(scene.TLM_PT_Additional)