From 6a6d383970a67aff4a683af62cdbc419bc253d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Wed, 30 Dec 2020 17:52:27 +0100 Subject: [PATCH 001/264] Update "Armory Props" object panel to Blender 2.9 layouts --- blender/arm/props_ui.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index d58b3012..8c1e3498 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -21,8 +21,9 @@ from arm.lightmapper.utility import icon from arm.lightmapper.properties.denoiser import oidn, optix import importlib -# Menu in object region + class ARM_PT_ObjectPropsPanel(bpy.types.Panel): + """Menu in object region.""" bl_label = "Armory Props" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -37,12 +38,13 @@ class ARM_PT_ObjectPropsPanel(bpy.types.Panel): if obj == None: return - layout.prop(obj, 'arm_export') + col = layout.column() + col.prop(obj, 'arm_export') if not obj.arm_export: return - layout.prop(obj, 'arm_spawn') - layout.prop(obj, 'arm_mobile') - layout.prop(obj, 'arm_animation_enabled') + 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') From a647263d1a12ec8cb47d45a839829619a6d6597b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Wed, 30 Dec 2020 17:53:35 +0100 Subject: [PATCH 002/264] Update "Armory Proxy" object panel to Blender 2.9 layouts --- blender/arm/props_ui.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index 8c1e3498..7f51d8de 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -2114,20 +2114,27 @@ class ARM_PT_ProxyPanel(bpy.types.Panel): layout.use_property_split = True layout.use_property_decorate = False layout.operator("arm.make_proxy") + obj = bpy.context.object - if obj != None and obj.proxy != None: - layout.label(text="Sync") - layout.prop(obj, "arm_proxy_sync_loc") - layout.prop(obj, "arm_proxy_sync_rot") - layout.prop(obj, "arm_proxy_sync_scale") - layout.prop(obj, "arm_proxy_sync_materials") - layout.prop(obj, "arm_proxy_sync_modifiers") - layout.prop(obj, "arm_proxy_sync_traits") - row = layout.row() + 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") - layout.operator("arm.proxy_toggle_all") - layout.operator("arm.proxy_apply_all") + + 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''' From 257f295b275aa545f22f3fa536bd1c19796d10f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Wed, 30 Dec 2020 17:54:44 +0100 Subject: [PATCH 003/264] Update "Armory Lod" object panel to Blender 2.9 layouts --- blender/arm/props_lod.py | 8 +++----- blender/arm/props_ui.py | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/blender/arm/props_lod.py b/blender/arm/props_lod.py index ef1b2e86..b483007c 100755 --- a/blender/arm/props_lod.py +++ b/blender/arm/props_lod.py @@ -34,24 +34,22 @@ class ArmLodListItem(bpy.types.PropertyGroup): class ARM_UL_LodList(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): - # We could write some code to decide which icon to use here... - custom_icon = 'OBJECT_DATAMODE' + layout.use_property_split = False - # Make sure your code supports all 3 layout types if self.layout_type in {'DEFAULT', 'COMPACT'}: layout.prop(item, "enabled_prop") name = item.name if name == '': name = 'None' row = layout.row() - row.label(text=name, icon=custom_icon) + row.label(text=name, icon='OBJECT_DATAMODE') col = row.column() col.alignment = 'RIGHT' col.label(text="{:.2f}".format(item.screen_size_prop)) elif self.layout_type in {'GRID'}: layout.alignment = 'CENTER' - layout.label(text="", icon = custom_icon) + layout.label(text="", icon='OBJECT_DATAMODE') class ArmLodListNewItem(bpy.types.Operator): # Add a new item to the list diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index 7f51d8de..de871621 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -1850,7 +1850,7 @@ class ARM_PT_BakePanel(bpy.types.Panel): layout.label(text="Warning! Overflow not yet supported") class ArmGenLodButton(bpy.types.Operator): - '''Automatically generate LoD levels''' + """Automatically generate LoD levels.""" bl_idname = 'arm.generate_lod' bl_label = 'Auto Generate' From 52cee7f0ce7d0070f50d35563598f92872f9339e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Wed, 30 Dec 2020 17:56:34 +0100 Subject: [PATCH 004/264] Update "Armory Traits" object panel to Blender 2.9 layouts --- blender/arm/props_traits.py | 57 +++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/blender/arm/props_traits.py b/blender/arm/props_traits.py index 377ed796..47fa91f8 100755 --- a/blender/arm/props_traits.py +++ b/blender/arm/props_traits.py @@ -2,6 +2,7 @@ import json import os import shutil import subprocess +from typing import Union import webbrowser from bpy.types import NodeTree @@ -82,7 +83,10 @@ class ArmTraitListItem(bpy.types.PropertyGroup): 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": @@ -96,7 +100,6 @@ class ARM_UL_TraitList(bpy.types.UIList): elif item.type_prop == "Logic Nodes": custom_icon = 'NODETREE' - # Make sure your code supports all 3 layout types if self.layout_type in {'DEFAULT', 'COMPACT'}: layout.prop(item, "enabled_prop") # Display " " for props without a name to right-align the @@ -635,7 +638,7 @@ class ARM_PT_TraitPanel(bpy.types.Panel): layout.use_property_split = True layout.use_property_decorate = False obj = bpy.context.object - draw_traits(layout, obj, is_object=True) + draw_traits_panel(layout, obj, is_object=True) class ARM_PT_SceneTraitPanel(bpy.types.Panel): bl_label = "Armory Scene Traits" @@ -648,7 +651,7 @@ class ARM_PT_SceneTraitPanel(bpy.types.Panel): layout.use_property_split = True layout.use_property_decorate = False obj = bpy.context.scene - draw_traits(layout, obj, is_object=False) + draw_traits_panel(layout, obj, is_object=False) class ARM_OT_CopyTraitsFromActive(bpy.types.Operator): bl_label = 'Copy Traits from Active Object' @@ -724,21 +727,24 @@ class ARM_OT_CopyTraitsFromActive(bpy.types.Operator): return {'INTERFACE'} -def draw_traits(layout, obj, is_object): - rows = 2 + +def draw_traits_panel(layout: bpy.types.UILayout, obj: Union[bpy.types.Object, bpy.types.Scene], + is_object: bool) -> None: + # Make the list bigger when there are a few traits + num_rows = 2 if len(obj.arm_traitlist) > 1: - rows = 4 + num_rows = 4 row = layout.row() - row.template_list("ARM_UL_TraitList", "The_List", obj, "arm_traitlist", obj, "arm_traitlist_index", rows=rows) + 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.is_object = is_object if is_object: - op = col.operator("arm_traitlist.delete_item", icon='REMOVE', text="")#.all = False + op = col.operator("arm_traitlist.delete_item", icon='REMOVE', text="") else: - op = col.operator("arm_traitlist.delete_item_scene", icon='REMOVE', text="")#.all = False + op = col.operator("arm_traitlist.delete_item_scene", icon='REMOVE', text="") op.is_object = is_object if len(obj.arm_traitlist) > 1: @@ -750,35 +756,30 @@ def draw_traits(layout, obj, is_object): 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] if item.type_prop == 'Haxe Script' or item.type_prop == 'Bundled Script': + row = layout.row(align=True) + row.alignment = 'EXPAND' + if item.type_prop == 'Haxe Script': - row = layout.row(align=True) - row.alignment = 'EXPAND' column = row.column(align=True) column.alignment = 'EXPAND' if item.class_name_prop == '': column.enabled = False - op = column.operator("arm.edit_script", icon="FILE_SCRIPT") - op.is_object = is_object - op = row.operator("arm.new_script") - op.is_object = is_object - op = row.operator("arm.refresh_scripts", text="Refresh") - else: # Bundled + column.operator("arm.edit_script", icon="FILE_SCRIPT").is_object = is_object + row.operator("arm.new_script").is_object = is_object + row.operator("arm.refresh_scripts", text="Refresh") + + # Bundled scripts + else: if item.class_name_prop == 'NavMesh': - row = layout.row(align=True) - row.alignment = 'EXPAND' - op = layout.operator("arm.generate_navmesh") - row = layout.row(align=True) - row.alignment = 'EXPAND' - column = row.column(align=True) - column.alignment = 'EXPAND' - if not item.class_name_prop == 'NavMesh': - op = column.operator("arm.edit_bundled_script", icon="FILE_SCRIPT") - op.is_object = is_object - op = row.operator("arm.refresh_scripts", text="Refresh") + row.operator("arm.generate_navmesh") + else: + row.operator("arm.edit_bundled_script", icon="FILE_SCRIPT").is_object = is_object + row.operator("arm.refresh_scripts", text="Refresh") # Default props item.name = item.class_name_prop From 4862054bcfb1761a085f1430b2984ea44e5f3e33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Wed, 30 Dec 2020 17:56:58 +0100 Subject: [PATCH 005/264] Reorder trait type selection --- blender/arm/props_traits.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/blender/arm/props_traits.py b/blender/arm/props_traits.py index 47fa91f8..d1c25c9a 100755 --- a/blender/arm/props_traits.py +++ b/blender/arm/props_traits.py @@ -119,13 +119,14 @@ class ArmTraitListNewItem(bpy.types.Operator): is_object: BoolProperty(name="Object Trait", description="Whether this is an object or scene trait", default=False) type_prop: EnumProperty( - items = [('Haxe Script', 'Haxe', 'Haxe Script'), - ('WebAssembly', 'Wasm', 'WebAssembly'), - ('UI Canvas', 'UI', 'UI Canvas'), - ('Bundled Script', 'Bundled', 'Bundled Script'), - ('Logic Nodes', 'Nodes', 'Logic Nodes') - ], - name = "Type") + name="Type", + items=[ + ('Haxe Script', 'Haxe', 'Haxe Script'), + ('Logic Nodes', 'Nodes', 'Logic Nodes'), + ('UI Canvas', 'UI', 'UI Canvas'), + ('Bundled Script', 'Bundled', 'Bundled Script'), + ('WebAssembly', 'Wasm', 'WebAssembly') + ]) def invoke(self, context, event): wm = context.window_manager From 14bca0ec80563898db665ea1751bf7b7fa0369ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Wed, 30 Dec 2020 17:57:23 +0100 Subject: [PATCH 006/264] Shorten too long node trait operator labels --- blender/arm/props_traits.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/blender/arm/props_traits.py b/blender/arm/props_traits.py index d1c25c9a..b0836246 100755 --- a/blender/arm/props_traits.py +++ b/blender/arm/props_traits.py @@ -834,7 +834,7 @@ def draw_traits_panel(layout: bpy.types.UILayout, obj: Union[bpy.types.Object, b # New column = row.column(align=True) column.alignment = 'EXPAND' - op = column.operator("arm.new_treenode", text="New Node Tree", icon="ADD") + op = column.operator("arm.new_treenode", text="New Tree", icon="ADD") op.is_object = is_object # At least one check is active Logic Node Editor is_check_logic_node_editor = False @@ -857,7 +857,7 @@ def draw_traits_panel(layout: bpy.types.UILayout, obj: Union[bpy.types.Object, b column.enabled = False else: column.enabled = is_check_logic_node_editor - op = column.operator("arm.edit_treenode", text="Edit Node Tree", icon="NODETREE") + op = column.operator("arm.edit_treenode", text="Edit Tree", icon="NODETREE") op.is_object = is_object # Get from Node Tree Editor column = row.column(align=True) @@ -866,7 +866,7 @@ def draw_traits_panel(layout: bpy.types.UILayout, obj: Union[bpy.types.Object, b column.enabled = False else: column.enabled = is_check_logic_node_editor - op = column.operator("arm.get_treenode", text="From Node Editor", icon="IMPORT") + op = column.operator("arm.get_treenode", text="From Editor", icon="IMPORT") op.is_object = is_object # Row for search From cab20d0a64bdacfae7f4d97bd8a091c5b4287ac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Mon, 4 Jan 2021 19:34:42 +0100 Subject: [PATCH 007/264] Principled BSDF: update input socket indices to Blender 2.9 --- blender/arm/material/cycles_nodes/nodes_shader.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/blender/arm/material/cycles_nodes/nodes_shader.py b/blender/arm/material/cycles_nodes/nodes_shader.py index d6114454..4e593bd0 100644 --- a/blender/arm/material/cycles_nodes/nodes_shader.py +++ b/blender/arm/material/cycles_nodes/nodes_shader.py @@ -41,7 +41,7 @@ def parse_addshader(node: bpy.types.ShaderNodeAddShader, out_socket: NodeSocket, def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: NodeSocket, state: ParserState) -> None: if state.parse_surface: - c.write_normal(node.inputs[19]) + c.write_normal(node.inputs[20]) state.out_basecol = c.parse_vector_input(node.inputs[0]) # subsurface = c.parse_vector_input(node.inputs[1]) # subsurface_radius = c.parse_vector_input(node.inputs[2]) @@ -62,11 +62,12 @@ def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: N if node.inputs[17].is_linked or node.inputs[17].default_value[0] != 0.0: state.out_emission = '({0}.x)'.format(c.parse_vector_input(node.inputs[17])) state.emission_found = True - # clearcoar_normal = c.parse_vector_input(node.inputs[20]) - # tangent = c.parse_vector_input(node.inputs[21]) + # clearcoar_normal = c.parse_vector_input(node.inputs[21]) + # tangent = c.parse_vector_input(node.inputs[22]) if state.parse_opacity: - if len(node.inputs) > 20: - state.out_opacity = c.parse_value_input(node.inputs[18]) + if len(node.inputs) > 21: + state.out_opacity = c.parse_value_input(node.inputs[19]) + def parse_bsdfdiffuse(node: bpy.types.ShaderNodeBsdfDiffuse, out_socket: NodeSocket, state: ParserState) -> None: if state.parse_surface: From c5e95224425e00b0277b4875a6778230aaa7c41e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Wed, 6 Jan 2021 17:23:21 +0100 Subject: [PATCH 008/264] Blender 2.9: Update operator options --- blender/arm/logicnode/arm_nodes.py | 8 ++++++++ blender/arm/logicnode/input/LN_on_swipe.py | 2 ++ blender/arm/nodes_logic.py | 7 ++++--- blender/arm/props_exporter.py | 2 +- blender/arm/props_ui.py | 6 +++--- 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/blender/arm/logicnode/arm_nodes.py b/blender/arm/logicnode/arm_nodes.py index 64c23121..d7920b40 100644 --- a/blender/arm/logicnode/arm_nodes.py +++ b/blender/arm/logicnode/arm_nodes.py @@ -112,6 +112,7 @@ class ArmNodeAddInputButton(bpy.types.Operator): """Add a new input socket to the node set by node_index.""" bl_idname = 'arm.node_add_input' bl_label = 'Add Input' + bl_options = {'UNDO', 'INTERNAL'} node_index: StringProperty(name='Node Index', default='') socket_type: StringProperty(name='Socket Type', default='NodeSocketShader') @@ -135,6 +136,7 @@ class ArmNodeAddInputValueButton(bpy.types.Operator): """Add new input""" bl_idname = 'arm.node_add_input_value' bl_label = 'Add Input' + bl_options = {'UNDO', 'INTERNAL'} node_index: StringProperty(name='Node Index', default='') socket_type: StringProperty(name='Socket Type', default='NodeSocketShader') @@ -148,6 +150,7 @@ class ArmNodeRemoveInputButton(bpy.types.Operator): """Remove last input""" bl_idname = 'arm.node_remove_input' bl_label = 'Remove Input' + bl_options = {'UNDO', 'INTERNAL'} node_index: StringProperty(name='Node Index', default='') def execute(self, context): @@ -163,6 +166,7 @@ class ArmNodeRemoveInputValueButton(bpy.types.Operator): """Remove last input""" bl_idname = 'arm.node_remove_input_value' bl_label = 'Remove Input' + bl_options = {'UNDO', 'INTERNAL'} node_index: StringProperty(name='Node Index', default='') def execute(self, context): @@ -178,6 +182,7 @@ class ArmNodeAddOutputButton(bpy.types.Operator): """Add a new output socket to the node set by node_index""" bl_idname = 'arm.node_add_output' bl_label = 'Add Output' + bl_options = {'UNDO', 'INTERNAL'} node_index: StringProperty(name='Node Index', default='') socket_type: StringProperty(name='Socket Type', default='NodeSocketShader') @@ -201,6 +206,7 @@ class ArmNodeRemoveOutputButton(bpy.types.Operator): """Remove last output""" bl_idname = 'arm.node_remove_output' bl_label = 'Remove Output' + bl_options = {'UNDO', 'INTERNAL'} node_index: StringProperty(name='Node Index', default='') def execute(self, context): @@ -216,6 +222,7 @@ class ArmNodeAddInputOutputButton(bpy.types.Operator): """Add new input and output""" bl_idname = 'arm.node_add_input_output' bl_label = 'Add Input Output' + bl_options = {'UNDO', 'INTERNAL'} node_index: StringProperty(name='Node Index', default='') in_socket_type: StringProperty(name='In Socket Type', default='NodeSocketShader') @@ -246,6 +253,7 @@ class ArmNodeRemoveInputOutputButton(bpy.types.Operator): """Remove last input and output""" bl_idname = 'arm.node_remove_input_output' bl_label = 'Remove Input Output' + bl_options = {'UNDO', 'INTERNAL'} node_index: StringProperty(name='Node Index', default='') def execute(self, context): diff --git a/blender/arm/logicnode/input/LN_on_swipe.py b/blender/arm/logicnode/input/LN_on_swipe.py index cfd9b17c..ed104a41 100644 --- a/blender/arm/logicnode/input/LN_on_swipe.py +++ b/blender/arm/logicnode/input/LN_on_swipe.py @@ -5,6 +5,7 @@ class NodeAddOutputButton(bpy.types.Operator): """Add 4 States""" bl_idname = 'arm.add_output_4_parameters' bl_label = 'Add 4 States' + bl_options = {'UNDO', 'INTERNAL'} node_index: StringProperty(name='Node Index', default='') socket_type: StringProperty(name='Socket Type', default='NodeSocketShader') name_format: StringProperty(name='Name Format', default='Output {0}') @@ -31,6 +32,7 @@ class NodeRemoveOutputButton(bpy.types.Operator): """Remove 4 last states""" bl_idname = 'arm.remove_output_4_parameters' bl_label = 'Remove 4 States' + bl_options = {'UNDO', 'INTERNAL'} node_index: StringProperty(name='Node Index', default='') def execute(self, context): diff --git a/blender/arm/nodes_logic.py b/blender/arm/nodes_logic.py index 3a44971f..4a61c1f7 100755 --- a/blender/arm/nodes_logic.py +++ b/blender/arm/nodes_logic.py @@ -58,13 +58,14 @@ class ARM_OT_AddNodeOverride(bpy.types.Operator): bl_idname = "arm.add_node_override" bl_label = "Add Node" bl_property = "type" + bl_options = {'INTERNAL'} type: StringProperty(name="NodeItem type") use_transform: BoolProperty(name="Use Transform") def invoke(self, context, event): bpy.ops.node.add_node('INVOKE_DEFAULT', type=self.type, use_transform=self.use_transform) - return {"FINISHED"} + return {'FINISHED'} @classmethod def description(cls, context, properties): @@ -261,7 +262,7 @@ class ARM_PT_Variables(bpy.types.Panel): setN.ntype = ID class ARMAddVarNode(bpy.types.Operator): - '''Add a linked node of that Variable''' + """Add a linked node of that Variable""" bl_idname = 'arm.add_var_node' bl_label = 'Add Get' bl_options = {'GRAB_CURSOR', 'BLOCKING'} @@ -296,7 +297,7 @@ class ARMAddVarNode(bpy.types.Operator): return({'FINISHED'}) class ARMAddSetVarNode(bpy.types.Operator): - '''Add a node to set this Variable''' + """Add a node to set this Variable""" bl_idname = 'arm.add_setvar_node' bl_label = 'Add Set' bl_options = {'GRAB_CURSOR', 'BLOCKING'} diff --git a/blender/arm/props_exporter.py b/blender/arm/props_exporter.py index 2b19a543..0dc2f143 100644 --- a/blender/arm/props_exporter.py +++ b/blender/arm/props_exporter.py @@ -358,7 +358,7 @@ class ArmExporterSpecialsMenu(bpy.types.Menu): layout.operator("arm.exporter_gpuprofile") class ArmoryExporterOpenFolderButton(bpy.types.Operator): - '''Open published folder''' + """Open published folder""" bl_idname = 'arm.exporter_open_folder' bl_label = 'Open Folder' diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index de871621..82b69bc4 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -270,7 +270,7 @@ class ARM_PT_ScenePropsPanel(bpy.types.Panel): row.prop(scene, 'arm_export') class InvalidateCacheButton(bpy.types.Operator): - '''Delete cached mesh data''' + """Delete cached mesh data""" bl_idname = "arm.invalidate_cache" bl_label = "Invalidate Cache" @@ -279,7 +279,7 @@ class InvalidateCacheButton(bpy.types.Operator): return{'FINISHED'} class InvalidateMaterialCacheButton(bpy.types.Operator): - '''Delete cached material data''' + """Delete cached material data""" bl_idname = "arm.invalidate_material_cache" bl_label = "Invalidate Cache" @@ -2246,7 +2246,7 @@ class ARM_OT_ShowFileVersionInfo(bpy.types.Operator): 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'} + bl_options = {'INTERNAL'} wrd = None From 80ea09671c4aca0e27ddf0edcd3c9a886c74af64 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Wed, 6 Jan 2021 20:26:43 +0100 Subject: [PATCH 009/264] Fix Exporter and conversions for Physics World in Blender 2.9.X --- .../armory/trait/physics/bullet/PhysicsWorld.hx | 14 +++++++++----- blender/arm/exporter.py | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Sources/armory/trait/physics/bullet/PhysicsWorld.hx b/Sources/armory/trait/physics/bullet/PhysicsWorld.hx index 22cf1cc5..581f7dda 100644 --- a/Sources/armory/trait/physics/bullet/PhysicsWorld.hx +++ b/Sources/armory/trait/physics/bullet/PhysicsWorld.hx @@ -53,7 +53,6 @@ class PhysicsWorld extends Trait { public var rbMap: Map; public var conMap: Map; public var timeScale = 1.0; - var timeStep = 1 / 60; var maxSteps = 1; public var solverIterations = 10; public var hitPointWorld = new Vec4(); @@ -68,7 +67,7 @@ class PhysicsWorld extends Trait { public static var physTime = 0.0; #end - public function new(timeScale = 1.0, timeStep = 1 / 60, solverIterations = 10) { + public function new(timeScale = 1.0, maxSteps = 10, solverIterations = 10) { super(); if (nullvec) { @@ -82,8 +81,7 @@ class PhysicsWorld extends Trait { sceneRemoved = false; this.timeScale = timeScale; - this.timeStep = timeStep; - maxSteps = timeStep < 1 / 60 ? 10 : 1; + this.maxSteps = maxSteps; this.solverIterations = solverIterations; // First scene @@ -270,7 +268,13 @@ class PhysicsWorld extends Trait { if (preUpdates != null) for (f in preUpdates) f(); - world.stepSimulation(timeStep, maxSteps, t); + //Bullet physics fixed timescale + var fixedTime = 1.0 / 60; + + //This condition must be satisfied to not loose time + var currMaxSteps = t < (fixedTime * maxSteps) ? maxSteps : 1; + + world.stepSimulation(t, currMaxSteps, fixedTime); updateContacts(); for (rb in rbMap) @:privateAccess rb.physicsUpdate(); diff --git a/blender/arm/exporter.py b/blender/arm/exporter.py index 5ee3a07f..f58039cf 100755 --- a/blender/arm/exporter.py +++ b/blender/arm/exporter.py @@ -2596,7 +2596,7 @@ class ArmoryExporter: rbw = self.scene.rigidbody_world if rbw is not None and rbw.enabled: - out_trait['parameters'] = [str(rbw.time_scale), str(1 / rbw.steps_per_second), str(rbw.solver_iterations)] + out_trait['parameters'] = [str(rbw.time_scale), str(rbw.substeps_per_frame), str(rbw.solver_iterations)] self.output['traits'].append(out_trait) From c69774402c1ed675dc9de48d3c3e61c2f4757186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Mon, 11 Jan 2021 19:51:15 +0100 Subject: [PATCH 010/264] Blender 2.9: Fix tilesheet operator exceptions when using the search menu --- blender/arm/props_tilesheet.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/blender/arm/props_tilesheet.py b/blender/arm/props_tilesheet.py index eeea1b4f..c6c58d42 100644 --- a/blender/arm/props_tilesheet.py +++ b/blender/arm/props_tilesheet.py @@ -60,6 +60,8 @@ class ArmTilesheetActionListDeleteItem(bpy.types.Operator): def poll(self, context): """ Enable if there's something in the list """ wrd = bpy.data.worlds['Arm'] + if len(wrd.arm_tilesheetlist) == 0: + return False trait = wrd.arm_tilesheetlist[wrd.arm_tilesheetlist_index] return len(trait.arm_tilesheetactionlist) > 0 @@ -81,6 +83,8 @@ class ArmTilesheetActionListMoveItem(bpy.types.Operator): # Move an item in the list bl_idname = "arm_tilesheetactionlist.move_item" bl_label = "Move an item in the list" + bl_options = {'INTERNAL'} + direction: EnumProperty( items=( ('UP', 'Up', ""), @@ -90,6 +94,8 @@ class ArmTilesheetActionListMoveItem(bpy.types.Operator): def poll(self, context): """ Enable if there's something in the list. """ wrd = bpy.data.worlds['Arm'] + if len(wrd.arm_tilesheetlist) == 0: + return False trait = wrd.arm_tilesheetlist[wrd.arm_tilesheetlist_index] return len(trait.arm_tilesheetactionlist) > 0 @@ -203,6 +209,8 @@ class ArmTilesheetListMoveItem(bpy.types.Operator): # Move an item in the list bl_idname = "arm_tilesheetlist.move_item" bl_label = "Move an item in the list" + bl_options = {'INTERNAL'} + direction: EnumProperty( items=( ('UP', 'Up', ""), From 4f25af45ba6d74ee15142dd9867ca9c784953f68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Mon, 11 Jan 2021 19:56:40 +0100 Subject: [PATCH 011/264] Cleanup --- blender/arm/props_tilesheet.py | 87 ++++++++++++++++------------------ 1 file changed, 42 insertions(+), 45 deletions(-) diff --git a/blender/arm/props_tilesheet.py b/blender/arm/props_tilesheet.py index c6c58d42..1f7d53b6 100644 --- a/blender/arm/props_tilesheet.py +++ b/blender/arm/props_tilesheet.py @@ -1,30 +1,26 @@ -import shutil import bpy -import os -import json -from bpy.types import Menu, Panel, UIList from bpy.props import * class ArmTilesheetActionListItem(bpy.types.PropertyGroup): name: StringProperty( - name="Name", - description="A name for this item", - default="Untitled") + name="Name", + description="A name for this item", + default="Untitled") start_prop: IntProperty( - name="Start", - description="A name for this item", - default=0) + name="Start", + description="A name for this item", + default=0) end_prop: IntProperty( - name="End", - description="A name for this item", - default=0) + name="End", + description="A name for this item", + default=0) loop_prop: BoolProperty( - name="Loop", - description="A name for this item", - default=True) + name="Loop", + description="A name for this item", + default=True) class ARM_UL_TilesheetActionList(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): @@ -52,13 +48,13 @@ class ArmTilesheetActionListNewItem(bpy.types.Operator): return{'FINISHED'} class ArmTilesheetActionListDeleteItem(bpy.types.Operator): - # Delete the selected item from the list + """Delete the selected item from the list""" bl_idname = "arm_tilesheetactionlist.delete_item" bl_label = "Deletes an item" @classmethod def poll(self, context): - """ Enable if there's something in the list """ + """Enable if there's something in the list""" wrd = bpy.data.worlds['Arm'] if len(wrd.arm_tilesheetlist) == 0: return False @@ -80,19 +76,20 @@ class ArmTilesheetActionListDeleteItem(bpy.types.Operator): return{'FINISHED'} class ArmTilesheetActionListMoveItem(bpy.types.Operator): - # Move an item in the list + """Move an item in the list""" bl_idname = "arm_tilesheetactionlist.move_item" bl_label = "Move an item in the list" bl_options = {'INTERNAL'} direction: EnumProperty( - items=( - ('UP', 'Up', ""), - ('DOWN', 'Down', ""),)) + items=( + ('UP', 'Up', ""), + ('DOWN', 'Down', "") + )) @classmethod def poll(self, context): - """ Enable if there's something in the list. """ + """Enable if there's something in the list""" wrd = bpy.data.worlds['Arm'] if len(wrd.arm_tilesheetlist) == 0: return False @@ -135,27 +132,27 @@ class ArmTilesheetActionListMoveItem(bpy.types.Operator): class ArmTilesheetListItem(bpy.types.PropertyGroup): name: StringProperty( - name="Name", - description="A name for this item", - default="Untitled") + name="Name", + description="A name for this item", + default="Untitled") tilesx_prop: IntProperty( - name="Tiles X", - description="A name for this item", - default=0) + name="Tiles X", + description="A name for this item", + default=0) tilesy_prop: IntProperty( - name="Tiles Y", - description="A name for this item", - default=0) + name="Tiles Y", + description="A name for this item", + default=0) framerate_prop: FloatProperty( - name="Frame Rate", - description="A name for this item", - default=4.0) + name="Frame Rate", + description="A name for this item", + default=4.0) arm_tilesheetactionlist: CollectionProperty(type=ArmTilesheetActionListItem) - arm_tilesheetactionlist_index: IntProperty(name="Index for my_list", default=0) + arm_tilesheetactionlist_index: IntProperty(name="Index for arm_tilesheetactionlist", default=0) class ARM_UL_TilesheetList(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): @@ -168,10 +165,10 @@ class ARM_UL_TilesheetList(bpy.types.UIList): elif self.layout_type in {'GRID'}: layout.alignment = 'CENTER' - layout.label(text="", icon = custom_icon) + layout.label(text="", icon=custom_icon) class ArmTilesheetListNewItem(bpy.types.Operator): - # Add a new item to the list + """Add a new item to the list""" bl_idname = "arm_tilesheetlist.new_item" bl_label = "Add a new item" @@ -182,7 +179,7 @@ class ArmTilesheetListNewItem(bpy.types.Operator): return{'FINISHED'} class ArmTilesheetListDeleteItem(bpy.types.Operator): - # Delete the selected item from the list + """Delete the selected item from the list""" bl_idname = "arm_tilesheetlist.delete_item" bl_label = "Deletes an item" @@ -206,15 +203,16 @@ class ArmTilesheetListDeleteItem(bpy.types.Operator): return{'FINISHED'} class ArmTilesheetListMoveItem(bpy.types.Operator): - # Move an item in the list + """Move an item in the list""" bl_idname = "arm_tilesheetlist.move_item" bl_label = "Move an item in the list" bl_options = {'INTERNAL'} direction: EnumProperty( - items=( - ('UP', 'Up', ""), - ('DOWN', 'Down', ""),)) + items=( + ('UP', 'Up', ""), + ('DOWN', 'Down', "") + )) @classmethod def poll(self, context): @@ -255,7 +253,6 @@ class ArmTilesheetListMoveItem(bpy.types.Operator): return{'FINISHED'} def register(): - bpy.utils.register_class(ArmTilesheetActionListItem) bpy.utils.register_class(ARM_UL_TilesheetActionList) bpy.utils.register_class(ArmTilesheetActionListNewItem) @@ -269,7 +266,7 @@ def register(): bpy.utils.register_class(ArmTilesheetListMoveItem) bpy.types.World.arm_tilesheetlist = CollectionProperty(type=ArmTilesheetListItem) - bpy.types.World.arm_tilesheetlist_index = IntProperty(name="Index for my_list", default=0) + bpy.types.World.arm_tilesheetlist_index = IntProperty(name="Index for arm_tilesheetlist", default=0) def unregister(): bpy.utils.unregister_class(ArmTilesheetListItem) From 492009883105150913850f0d28f4221e458d0be0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Mon, 11 Jan 2021 19:57:29 +0100 Subject: [PATCH 012/264] Blender 2.9: Fix another exception when using the search menu --- blender/arm/logicnode/arm_nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/logicnode/arm_nodes.py b/blender/arm/logicnode/arm_nodes.py index d7920b40..e99b8bd2 100644 --- a/blender/arm/logicnode/arm_nodes.py +++ b/blender/arm/logicnode/arm_nodes.py @@ -273,7 +273,7 @@ class ArmNodeRemoveInputOutputButton(bpy.types.Operator): class ArmNodeSearch(bpy.types.Operator): bl_idname = "arm.node_search" bl_label = "Search..." - bl_options = {"REGISTER"} + bl_options = {"REGISTER", "INTERNAL"} bl_property = "item" def get_search_items(self, context): From 1b9f010c575f735b75ec061dff2db1e9b9efa24d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 14 Jan 2021 20:56:04 +0100 Subject: [PATCH 013/264] Blender 2.9: Improve project window panel UI --- blender/arm/props_ui.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index 82b69bc4..1e89521e 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -775,11 +775,15 @@ class ARM_PT_ProjectWindowPanel(bpy.types.Panel): layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] layout.prop(wrd, 'arm_winmode') - layout.prop(wrd, 'arm_winresize') - col = layout.column() - col.enabled = wrd.arm_winresize - col.prop(wrd, 'arm_winmaximize') - layout.prop(wrd, 'arm_winminimize') + + 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): From a98559ea7fbf5c243dfb4fd325e79b2be2cae94f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 14 Jan 2021 20:57:44 +0100 Subject: [PATCH 014/264] Blender 2.9: Improve material blending panel UI --- blender/arm/props.py | 6 +++--- blender/arm/props_ui.py | 10 ++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/blender/arm/props.py b/blender/arm/props.py index ae6aa0a9..ab91f129 100755 --- a/blender/arm/props.py +++ b/blender/arm/props.py @@ -391,7 +391,7 @@ def init_properties(): ('destination_color', 'Destination Color', 'Destination Color'), ('inverse_source_color', 'Inverse Source Color', 'Inverse Source Color'), ('inverse_destination_color', 'Inverse Destination Color', 'Inverse Destination Color')], - name='Source', default='blend_one', description='Blending factor', update=assets.invalidate_shader_cache) + name='Source (Alpha)', default='blend_one', description='Blending factor', update=assets.invalidate_shader_cache) bpy.types.Material.arm_blending_destination_alpha = EnumProperty( items=[('blend_one', 'One', 'One'), ('blend_zero', 'Zero', 'Zero'), @@ -403,14 +403,14 @@ def init_properties(): ('destination_color', 'Destination Color', 'Destination Color'), ('inverse_source_color', 'Inverse Source Color', 'Inverse Source Color'), ('inverse_destination_color', 'Inverse Destination Color', 'Inverse Destination Color')], - name='Destination', default='blend_one', description='Blending factor', update=assets.invalidate_shader_cache) + name='Destination (Alpha)', default='blend_one', description='Blending factor', update=assets.invalidate_shader_cache) bpy.types.Material.arm_blending_operation_alpha = EnumProperty( items=[('add', 'Add', 'Add'), ('subtract', 'Subtract', 'Subtract'), ('reverse_subtract', 'Reverse Subtract', 'Reverse Subtract'), ('min', 'Min', 'Min'), ('max', 'Max', 'Max')], - name='Operation', default='add', description='Blending operation', update=assets.invalidate_shader_cache) + name='Operation (Alpha)', default='add', description='Blending operation', update=assets.invalidate_shader_cache) # For scene bpy.types.Scene.arm_export = BoolProperty(name="Export", description="Export scene data", default=True) bpy.types.Scene.arm_terrain_textures = StringProperty(name="Textures", description="Set root folder for terrain assets", default="//Bundled/", subtype="DIR_PATH") diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index 1e89521e..d0db4f08 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -369,7 +369,7 @@ class ARM_PT_MaterialBlendingPropsPanel(bpy.types.Panel): bl_parent_id = "ARM_PT_MaterialPropsPanel" def draw_header(self, context): - if context.material == None: + if context.material is None: return self.layout.prop(context.material, 'arm_blending', text="") @@ -378,16 +378,18 @@ class ARM_PT_MaterialBlendingPropsPanel(bpy.types.Panel): layout.use_property_split = True layout.use_property_decorate = False mat = bpy.context.material - if mat == None: + if mat is None: return flow = layout.grid_flow() flow.enabled = mat.arm_blending - col = flow.column() + col = flow.column(align=True) col.prop(mat, 'arm_blending_source') col.prop(mat, 'arm_blending_destination') col.prop(mat, 'arm_blending_operation') - col = flow.column() + 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') From 4c0f0f416115c40492dc5ead0e36d63a5ccb5526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 14 Jan 2021 21:06:37 +0100 Subject: [PATCH 015/264] Blender 2.9: slightly improve renderpath "Renderer" panel UI --- blender/arm/props_ui.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index d0db4f08..763641fa 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -1106,17 +1106,23 @@ class ARM_PT_RenderPathRendererPanel(bpy.types.Panel): layout.prop(rpdat, 'arm_tess_shadows_outer') layout.prop(rpdat, 'arm_particles') - layout.prop(rpdat, 'arm_skin') - row = layout.row() - row.enabled = rpdat.arm_skin == 'On' - row.prop(rpdat, 'arm_skin_max_bones_auto') - row = layout.row() + 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.prop(rpdat, "rp_hdr") - layout.prop(rpdat, "rp_stereo") - layout.prop(rpdat, 'arm_culling') - layout.prop(rpdat, 'rp_pp') + 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" From 406d48eb7c04778219f96a1dd5b739f2b9351a29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 14 Jan 2021 21:13:02 +0100 Subject: [PATCH 016/264] Blender 2.9: Improve renderpath world panel UI --- blender/arm/props_ui.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index 763641fa..fe71b35b 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -1218,16 +1218,22 @@ class ARM_PT_RenderPathWorldPanel(bpy.types.Panel): rpdat = wrd.arm_rplist[wrd.arm_rplist_index] layout.prop(rpdat, "rp_background") - layout.prop(rpdat, 'arm_irradiance') + col = layout.column() - col.enabled = rpdat.arm_irradiance - col.prop(rpdat, 'arm_radiance') + col.prop(rpdat, 'arm_irradiance') colb = col.column() - colb.enabled = rpdat.arm_radiance - colb.prop(rpdat, 'arm_radiance_size') + 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') - layout.prop(rpdat, "rp_water") + 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') From 133777f7e57bef1f718258c228ecb6bcdad6d7dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 14 Jan 2021 21:18:46 +0100 Subject: [PATCH 017/264] Blender 2.9: Improve renderpath postprocess panel UI --- blender/arm/props_ui.py | 61 ++++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index fe71b35b..6e791618 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -1269,27 +1269,35 @@ class ARM_PT_RenderPathPostProcessPanel(bpy.types.Panel): rpdat = wrd.arm_rplist[wrd.arm_rplist_index] layout.enabled = rpdat.rp_render_to_texture - row = layout.row() - row.prop(rpdat, "rp_antialiasing") - layout.prop(rpdat, "rp_supersampling") - layout.prop(rpdat, 'arm_rp_resolution') + 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': - layout.prop(rpdat, 'arm_rp_resolution_size') - layout.prop(rpdat, 'arm_rp_resolution_filter') - layout.prop(rpdat, 'rp_dynres') + col.prop(rpdat, 'arm_rp_resolution_size') + col.prop(rpdat, 'arm_rp_resolution_filter') + col.prop(rpdat, 'rp_dynres') layout.separator() - row = layout.row() - row.prop(rpdat, "rp_ssgi") + col = layout.column() - col.enabled = rpdat.rp_ssgi != 'Off' - col.prop(rpdat, 'arm_ssgi_half_res') - col.prop(rpdat, 'arm_ssgi_rays') - col.prop(rpdat, 'arm_ssgi_radius') - col.prop(rpdat, 'arm_ssgi_strength') - col.prop(rpdat, 'arm_ssgi_max_steps') + 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() - layout.prop(rpdat, "rp_ssr") + 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') @@ -1298,33 +1306,42 @@ class ARM_PT_RenderPathPostProcessPanel(bpy.types.Panel): col.prop(rpdat, 'arm_ssr_falloff_exp') col.prop(rpdat, 'arm_ssr_jitter') layout.separator() - layout.prop(rpdat, 'arm_ssrs') + col = layout.column() + col.prop(rpdat, 'arm_ssrs') + col = col.column() col.enabled = rpdat.arm_ssrs col.prop(rpdat, 'arm_ssrs_ray_step') - layout.prop(rpdat, 'arm_micro_shadowing') layout.separator() - layout.prop(rpdat, "rp_bloom") + 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() - layout.prop(rpdat, "rp_motionblur") + 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() - layout.prop(rpdat, "rp_volumetriclight") + 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() - layout.prop(rpdat, "rp_chromatic_aberration") + 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') From b4f0df6367985d0d479be40492ae9628a10e2b97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 14 Jan 2021 21:21:01 +0100 Subject: [PATCH 018/264] Blender 2.9: Redesign renderpath compositor panel UI --- blender/arm/props_ui.py | 74 +++++++++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 29 deletions(-) diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index 6e791618..4de624e3 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -1374,45 +1374,49 @@ class ARM_PT_RenderPathCompositorPanel(bpy.types.Panel): layout.enabled = rpdat.rp_compositornodes layout.prop(rpdat, 'arm_tonemap') - layout.prop(rpdat, 'arm_letterbox') + col = layout.column() - col.enabled = rpdat.arm_letterbox - col.prop(rpdat, 'arm_letterbox_size') - layout.prop(rpdat, 'arm_sharpen') + 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.enabled = rpdat.arm_sharpen - col.prop(rpdat, 'arm_sharpen_strength') - layout.prop(rpdat, 'arm_fisheye') - layout.prop(rpdat, 'arm_vignette') - col = layout.column() - col.enabled = rpdat.arm_vignette - col.prop(rpdat, 'arm_vignette_strength') - layout.prop(rpdat, 'arm_lensflare') - layout.prop(rpdat, 'arm_grain') - col = layout.column() - col.enabled = rpdat.arm_grain - col.prop(rpdat, 'arm_grain_strength') - layout.prop(rpdat, 'arm_fog') - col = layout.column(align=True) + 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() - layout.prop(rpdat, "rp_autoexposure") + col = layout.column() - col.enabled = rpdat.rp_autoexposure - col.prop(rpdat, 'arm_autoexposure_strength', text='Strength') - col.prop(rpdat, 'arm_autoexposure_speed', text='Speed') - layout.prop(rpdat, 'arm_lens_texture') + 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') + + col = layout.column() + col.prop(rpdat, 'arm_lens_texture') if rpdat.arm_lens_texture != "": - layout.prop(rpdat, 'arm_lens_texture_masking') + col.prop(rpdat, 'arm_lens_texture_masking') if rpdat.arm_lens_texture_masking: - layout.prop(rpdat, 'arm_lens_texture_masking_centerMinClip') - layout.prop(rpdat, 'arm_lens_texture_masking_centerMaxClip') - layout.prop(rpdat, 'arm_lens_texture_masking_luminanceMin') - layout.prop(rpdat, 'arm_lens_texture_masking_luminanceMax') - layout.prop(rpdat, 'arm_lens_texture_masking_brightnessExp') + 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): @@ -2580,6 +2584,18 @@ def draw_custom_node_menu(self, context): 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) From 40efd58214b61b7805920ac95726d6f695590495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 14 Jan 2021 21:24:42 +0100 Subject: [PATCH 019/264] Blender 2.9: Improve trait list UI and fix checkbox position --- blender/arm/props_traits.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/blender/arm/props_traits.py b/blender/arm/props_traits.py index b0836246..9e2943dc 100755 --- a/blender/arm/props_traits.py +++ b/blender/arm/props_traits.py @@ -101,16 +101,20 @@ class ARM_UL_TraitList(bpy.types.UIList): custom_icon = 'NODETREE' if self.layout_type in {'DEFAULT', 'COMPACT'}: - layout.prop(item, "enabled_prop") + 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 - layout.label(text=item.name if item.name != "" else " ", icon=custom_icon, icon_value=custom_icon_value) + 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) - layout.prop(item, "fake_user", text="", icon="FAKE_USER_ON" if item.fake_user else "FAKE_USER_OFF") + row = layout.row(align=True) + row.scale_x = 1.2 + 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" From b7903dbef1b27e1be00cd2a55634387f18eccb64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 14 Jan 2021 22:14:10 +0100 Subject: [PATCH 020/264] Change canvas icon according to the canvas node category --- blender/arm/props_traits.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/props_traits.py b/blender/arm/props_traits.py index 9e2943dc..044b0c0f 100755 --- a/blender/arm/props_traits.py +++ b/blender/arm/props_traits.py @@ -94,7 +94,7 @@ class ARM_UL_TraitList(bpy.types.UIList): elif item.type_prop == "WebAssembly": custom_icon_value = icons_dict["wasm"].icon_id elif item.type_prop == "UI Canvas": - custom_icon = "OBJECT_DATAMODE" + custom_icon = "NODE_COMPOSITING" elif item.type_prop == "Bundled Script": custom_icon_value = icons_dict["bundle"].icon_id elif item.type_prop == "Logic Nodes": From fb88361c5bb9672418bc0ade3febf969e278c622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 14 Jan 2021 22:14:50 +0100 Subject: [PATCH 021/264] Blender 2.9: Simplify and improve trait panel UI --- blender/arm/props_traits.py | 104 +++++++++++++----------------------- 1 file changed, 36 insertions(+), 68 deletions(-) diff --git a/blender/arm/props_traits.py b/blender/arm/props_traits.py index 044b0c0f..ae475444 100755 --- a/blender/arm/props_traits.py +++ b/blender/arm/props_traits.py @@ -765,26 +765,25 @@ def draw_traits_panel(layout: bpy.types.UILayout, obj: Union[bpy.types.Object, b if obj.arm_traitlist_index >= 0 and len(obj.arm_traitlist) > 0: item = obj.arm_traitlist[obj.arm_traitlist_index] - if item.type_prop == 'Haxe Script' or item.type_prop == 'Bundled Script': - row = layout.row(align=True) - row.alignment = 'EXPAND' + 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.alignment = 'EXPAND' - if item.class_name_prop == '': - column.enabled = False + column.enabled = item.class_name_prop != '' column.operator("arm.edit_script", icon="FILE_SCRIPT").is_object = is_object - row.operator("arm.new_script").is_object = is_object - row.operator("arm.refresh_scripts", text="Refresh") # Bundled scripts else: if item.class_name_prop == 'NavMesh': - row.operator("arm.generate_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") + + row.operator("arm.refresh_scripts", text="Refresh", icon="FILE_REFRESH") # Default props item.name = item.class_name_prop @@ -799,86 +798,56 @@ def draw_traits_panel(layout: bpy.types.UILayout, obj: Union[bpy.types.Object, b 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") - row = layout.row(align=True) - row.alignment = 'EXPAND' - column = row.column(align=True) - column.alignment = 'EXPAND' - if item.class_name_prop == '': - column.enabled = False - # op = column.operator("arm.edit_script", icon="FILE_SCRIPT") - # op.is_object = is_object - op = row.operator("arm.new_wasm") - # op.is_object = is_object - op = row.operator("arm.refresh_scripts", text="Refresh") elif item.type_prop == 'UI Canvas': item.name = item.canvas_name_prop - row = layout.row(align=True) - row.alignment = 'EXPAND' + row.operator("arm.new_canvas", icon="FILE_NEW").is_object = is_object column = row.column(align=True) - column.alignment = 'EXPAND' - if item.canvas_name_prop == '': - column.enabled = False - op = column.operator("arm.edit_canvas", icon="FILE_SCRIPT") - op.is_object = is_object - op = row.operator("arm.new_canvas") - op.is_object = is_object - op = row.operator("arm.refresh_canvas_list", text="Refresh") + 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': - # Row for buttons - row = layout.row(align=True) - row.alignment = 'EXPAND' - # New - column = row.column(align=True) - column.alignment = 'EXPAND' - op = column.operator("arm.new_treenode", text="New Tree", icon="ADD") - op.is_object = is_object - # At least one check is active Logic Node Editor - is_check_logic_node_editor = False - context_screen = bpy.context.screen - # Loop for all spaces - if context_screen is not None: - areas = context_screen.areas + # 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_check_logic_node_editor = True + is_editor_active = True break - if is_check_logic_node_editor: + if is_editor_active: break - # Edit - column = row.column(align=True) - column.alignment = 'EXPAND' - if item.node_tree_prop is None: - column.enabled = False - else: - column.enabled = is_check_logic_node_editor - op = column.operator("arm.edit_treenode", text="Edit Tree", icon="NODETREE") - op.is_object = is_object - # Get from Node Tree Editor - column = row.column(align=True) - column.alignment = 'EXPAND' - if item is None: - column.enabled = False - else: - column.enabled = is_check_logic_node_editor - op = column.operator("arm.get_treenode", text="From Editor", icon="IMPORT") - op.is_object = is_object - # Row for search + 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': - # Props + if item.arm_traitpropslist: layout.label(text="Trait Properties:") if item.arm_traitpropswarnings: @@ -888,7 +857,6 @@ def draw_traits_panel(layout: bpy.types.UILayout, obj: Union[bpy.types.Object, b for warning in item.arm_traitpropswarnings: box.label(text=warning.warning) - propsrow = layout.row() propsrows = max(len(item.arm_traitpropslist), 6) row = layout.row() row.template_list("ARM_UL_PropList", "The_List", item, "arm_traitpropslist", item, "arm_traitpropslist_index", rows=propsrows) From 820de42e8397ef54efc234d636614e6b47ead61c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 14 Jan 2021 22:17:42 +0100 Subject: [PATCH 022/264] Blender 2.9: Fix lod list layout --- blender/arm/props_lod.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/blender/arm/props_lod.py b/blender/arm/props_lod.py index b483007c..e1da2a82 100755 --- a/blender/arm/props_lod.py +++ b/blender/arm/props_lod.py @@ -37,11 +37,12 @@ class ARM_UL_LodList(bpy.types.UIList): layout.use_property_split = False if self.layout_type in {'DEFAULT', 'COMPACT'}: - layout.prop(item, "enabled_prop") + row = layout.row() + row.separator(factor=0.1) + row.prop(item, "enabled_prop") name = item.name if name == '': name = 'None' - row = layout.row() row.label(text=name, icon='OBJECT_DATAMODE') col = row.column() col.alignment = 'RIGHT' From 04fdcc550005140f7806d07cb46f1e9da1fba859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 14 Jan 2021 22:28:58 +0100 Subject: [PATCH 023/264] Cleanup --- blender/arm/props_traits.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/blender/arm/props_traits.py b/blender/arm/props_traits.py index ae475444..fa78b108 100755 --- a/blender/arm/props_traits.py +++ b/blender/arm/props_traits.py @@ -639,11 +639,8 @@ class ARM_PT_TraitPanel(bpy.types.Panel): bl_context = "object" def draw(self, context): - layout = self.layout - layout.use_property_split = True - layout.use_property_decorate = False obj = bpy.context.object - draw_traits_panel(layout, obj, is_object=True) + draw_traits_panel(self.layout, obj, is_object=True) class ARM_PT_SceneTraitPanel(bpy.types.Panel): bl_label = "Armory Scene Traits" @@ -652,11 +649,8 @@ class ARM_PT_SceneTraitPanel(bpy.types.Panel): bl_context = "scene" def draw(self, context): - layout = self.layout - layout.use_property_split = True - layout.use_property_decorate = False obj = bpy.context.scene - draw_traits_panel(layout, obj, is_object=False) + draw_traits_panel(self.layout, obj, is_object=False) class ARM_OT_CopyTraitsFromActive(bpy.types.Operator): bl_label = 'Copy Traits from Active Object' @@ -735,6 +729,9 @@ class ARM_OT_CopyTraitsFromActive(bpy.types.Operator): 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: From fa7f58e4dd3925c144dda38d66d1b6d9fd9aa6ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 14 Jan 2021 22:46:22 +0100 Subject: [PATCH 024/264] Ensure that only logic node trees show up as node traits --- blender/arm/props_traits.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/blender/arm/props_traits.py b/blender/arm/props_traits.py index fa78b108..7dd5c429 100755 --- a/blender/arm/props_traits.py +++ b/blender/arm/props_traits.py @@ -62,6 +62,10 @@ def update_trait_group(self, context): 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) @@ -77,7 +81,7 @@ class ArmTraitListItem(bpy.types.PropertyGroup): 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) + 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) From ee1b55184cb7d0c8d004ad1f829ddcaa044bd6fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Fri, 15 Jan 2021 19:26:31 +0100 Subject: [PATCH 025/264] Move icon code into module This, combined with lazy loading, has the advantage of using icons in property definitions before the actual registration code runs --- blender/arm/nodes_logic.py | 3 ++- blender/arm/props_traits.py | 16 +++++----------- blender/arm/props_ui.py | 3 ++- blender/arm/ui_icons.py | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 13 deletions(-) create mode 100644 blender/arm/ui_icons.py diff --git a/blender/arm/nodes_logic.py b/blender/arm/nodes_logic.py index 4a61c1f7..12f605fb 100755 --- a/blender/arm/nodes_logic.py +++ b/blender/arm/nodes_logic.py @@ -8,6 +8,7 @@ import arm.logicnode.arm_nodes as arm_nodes import arm.logicnode.replacement import arm.logicnode import arm.props_traits +import arm.ui_icons as ui_icons import arm.utils registered_nodes = [] @@ -174,7 +175,7 @@ class ARM_PT_LogicNodePanel(bpy.types.Panel): layout.operator('arm.open_node_documentation', icon='HELP') column = layout.column(align=True) column.operator('arm.open_node_python_source', icon='FILE_SCRIPT') - column.operator('arm.open_node_haxe_source', icon_value=arm.props_traits.icons_dict['haxe'].icon_id) + column.operator('arm.open_node_haxe_source', icon_value=ui_icons.get_id("haxe")) class ArmOpenNodeHaxeSource(bpy.types.Operator): diff --git a/blender/arm/props_traits.py b/blender/arm/props_traits.py index 7dd5c429..2353c1da 100755 --- a/blender/arm/props_traits.py +++ b/blender/arm/props_traits.py @@ -11,6 +11,7 @@ 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 @@ -94,13 +95,13 @@ class ARM_UL_TraitList(bpy.types.UIList): custom_icon = "NONE" custom_icon_value = 0 if item.type_prop == "Haxe Script": - custom_icon_value = icons_dict["haxe"].icon_id + custom_icon_value = ui_icons.get_id("haxe") elif item.type_prop == "WebAssembly": - custom_icon_value = icons_dict["wasm"].icon_id + custom_icon_value = ui_icons.get_id("wasm") elif item.type_prop == "UI Canvas": custom_icon = "NODE_COMPOSITING" elif item.type_prop == "Bundled Script": - custom_icon_value = icons_dict["bundle"].icon_id + custom_icon_value = ui_icons.get_id("bundle") elif item.type_prop == "Logic Nodes": custom_icon = 'NODETREE' @@ -862,8 +863,8 @@ def draw_traits_panel(layout: bpy.types.UILayout, obj: Union[bpy.types.Object, b row = layout.row() row.template_list("ARM_UL_PropList", "The_List", item, "arm_traitpropslist", item, "arm_traitpropslist_index", rows=propsrows) + def register(): - global icons_dict bpy.utils.register_class(ArmTraitListItem) bpy.utils.register_class(ARM_UL_TraitList) bpy.utils.register_class(ArmTraitListNewItem) @@ -891,14 +892,8 @@ def register(): bpy.types.Scene.arm_traitlist = CollectionProperty(type=ArmTraitListItem) bpy.types.Scene.arm_traitlist_index = IntProperty(name="Index for arm_traitlist", default=0) - icons_dict = bpy.utils.previews.new() - icons_dir = os.path.join(os.path.dirname(__file__), "custom_icons") - icons_dict.load("haxe", os.path.join(icons_dir, "haxe.png"), 'IMAGE') - icons_dict.load("wasm", os.path.join(icons_dir, "wasm.png"), 'IMAGE') - icons_dict.load("bundle", os.path.join(icons_dir, "bundle.png"), 'IMAGE') def unregister(): - global icons_dict bpy.utils.unregister_class(ARM_OT_CopyTraitsFromActive) bpy.utils.unregister_class(ArmTraitListItem) bpy.utils.unregister_class(ARM_UL_TraitList) @@ -920,4 +915,3 @@ def unregister(): bpy.utils.unregister_class(ArmRefreshCanvasListButton) bpy.utils.unregister_class(ARM_PT_TraitPanel) bpy.utils.unregister_class(ARM_PT_SceneTraitPanel) - bpy.utils.previews.remove(icons_dict) diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index 4de624e3..304f635f 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -15,6 +15,7 @@ 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 from arm.lightmapper.utility import icon @@ -2574,7 +2575,7 @@ def draw_custom_node_menu(self, context): 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=arm.props_traits.icons_dict['haxe'].icon_id) + 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': diff --git a/blender/arm/ui_icons.py b/blender/arm/ui_icons.py new file mode 100644 index 00000000..f9364dd7 --- /dev/null +++ b/blender/arm/ui_icons.py @@ -0,0 +1,34 @@ +""" +Blender user interface icon handling. +""" +import os.path +from typing import Optional + +import bpy.utils.previews + +_icons_dict: Optional[bpy.utils.previews.ImagePreviewCollection] = None +"""Dictionary of all loaded icons, or `None` if not loaded""" + +_icons_dir = os.path.join(os.path.dirname(__file__), "custom_icons") +"""Directory of the icon files""" + + +def _load_icons() -> None: + """(Re)loads all icons""" + global _icons_dict + + if _icons_dict is not None: + bpy.utils.previews.remove(_icons_dict) + + _icons_dict = bpy.utils.previews.new() + _icons_dict.load("armory", os.path.join(_icons_dir, "armory.png"), 'IMAGE') + _icons_dict.load("bundle", os.path.join(_icons_dir, "bundle.png"), 'IMAGE') + _icons_dict.load("haxe", os.path.join(_icons_dir, "haxe.png"), 'IMAGE') + _icons_dict.load("wasm", os.path.join(_icons_dir, "wasm.png"), 'IMAGE') + + +def get_id(identifier: str) -> int: + """Returns the icon ID from the given identifier""" + if _icons_dict is None: + _load_icons() + return _icons_dict[identifier].icon_id From 7b55f3d8f57894d87f8518b742e20af2c8f166ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sun, 17 Jan 2021 16:53:17 +0100 Subject: [PATCH 026/264] Blender 2.9: Improve `Add Trait` operator UI --- blender/arm/props_traits.py | 47 ++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/blender/arm/props_traits.py b/blender/arm/props_traits.py index 2353c1da..2b1c0115 100755 --- a/blender/arm/props_traits.py +++ b/blender/arm/props_traits.py @@ -15,7 +15,19 @@ import arm.ui_icons as ui_icons import arm.utils import arm.write_data as write_data -icons_dict: bpy.utils.previews.ImagePreviewCollection +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') + +PROP_TYPES_ENUM = [ + ('Haxe Script', 'Haxe', 'Haxe script', ICON_HAXE, 0), + ('Logic Nodes', 'Nodes', 'Logic nodes (visual scripting)', ICON_NODES, 1), + ('UI Canvas', 'UI', 'User interface', ICON_CANVAS, 2), + ('Bundled Script', 'Bundled', 'Premade script with common functionality', ICON_BUNDLED, 3), + ('WebAssembly', 'Wasm', 'WebAssembly', ICON_WASM, 4) +] def trigger_recompile(self, context): @@ -71,14 +83,7 @@ class ArmTraitListItem(bpy.types.PropertyGroup): enabled_prop: BoolProperty(name="", description="A name for this item", default=True, update=trigger_recompile) is_object: BoolProperty(name="", default=True) fake_user: BoolProperty(name="Fake User", description="Export this trait even if it is deactivated", default=False) - type_prop: EnumProperty( - items = [('Haxe Script', 'Haxe', 'Haxe Script'), - ('WebAssembly', 'Wasm', 'WebAssembly'), - ('UI Canvas', 'UI', 'UI Canvas'), - ('Bundled Script', 'Bundled', 'Bundled Script'), - ('Logic Nodes', 'Nodes', 'Logic Nodes') - ], - name = "Type") + 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) @@ -118,34 +123,28 @@ class ARM_UL_TraitList(bpy.types.UIList): layout.label(text="", icon=custom_icon, icon_value=custom_icon_value) row = layout.row(align=True) - row.scale_x = 1.2 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 = "New Trait Item" + bl_label = "Add Trait" bl_description = "Add a new trait item to the list" - is_object: BoolProperty(name="Object Trait", description="Whether this is an object or scene trait", default=False) - type_prop: EnumProperty( - name="Type", - items=[ - ('Haxe Script', 'Haxe', 'Haxe Script'), - ('Logic Nodes', 'Nodes', 'Logic Nodes'), - ('UI Canvas', 'UI', 'UI Canvas'), - ('Bundled Script', 'Bundled', 'Bundled Script'), - ('WebAssembly', 'Wasm', 'WebAssembly') - ]) + 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) def invoke(self, context, event): wm = context.window_manager - return wm.invoke_props_dialog(self) + return wm.invoke_props_dialog(self, width=400) def draw(self, context): layout = self.layout # Todo: show is_object property when called from operator search menu # layout.prop(self, "is_object") - layout.prop(self, "type_prop", expand=True) + + row = layout.row() + row.scale_y = 1.3 + row.prop(self, "type_prop", expand=True) def execute(self, context): if self.is_object: @@ -162,7 +161,7 @@ class ArmTraitListNewItem(bpy.types.Operator): class ArmTraitListDeleteItem(bpy.types.Operator): """Delete the selected item from the list""" bl_idname = "arm_traitlist.delete_item" - bl_label = "Deletes an item" + bl_label = "Remove Trait" bl_options = {'INTERNAL'} is_object: BoolProperty(name="", description="A name for this item", default=False) From ffd3d6e82434db4f840cb59f410693439f1218ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sun, 17 Jan 2021 16:54:08 +0100 Subject: [PATCH 027/264] Show is_object property when `Add Trait` is not invoked from the UI --- blender/arm/props_traits.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/blender/arm/props_traits.py b/blender/arm/props_traits.py index 2b1c0115..e10bf283 100755 --- a/blender/arm/props_traits.py +++ b/blender/arm/props_traits.py @@ -133,14 +133,19 @@ class ArmTraitListNewItem(bpy.types.Operator): 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 - # Todo: show is_object property when called from operator search menu - # layout.prop(self, "is_object") + + if self.invoked_by_search: + row = layout.row() + row.prop(self, "is_object") row = layout.row() row.scale_y = 1.3 @@ -590,8 +595,6 @@ class ArmNewCanvasDialog(bpy.types.Operator): self.canvas_name = self.canvas_name.replace(' ', '') write_data.write_canvasjson(self.canvas_name) arm.utils.fetch_script_names() - # Todo: create new trait item when called from operator search - # menu, then remove 'INTERNAL' from bl_options item = obj.arm_traitlist[obj.arm_traitlist_index] item.canvas_name_prop = self.canvas_name return {'FINISHED'} @@ -746,6 +749,7 @@ def draw_traits_panel(layout: bpy.types.UILayout, obj: Union[bpy.types.Object, b 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="") From 562d39c2035fa46f499d6aaeafe8954ab43b5aaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sun, 17 Jan 2021 20:24:43 +0100 Subject: [PATCH 028/264] Blender 2.9: Improve Armory player panel UI --- blender/arm/props_ui.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index 304f635f..fc31a31d 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -406,20 +406,25 @@ class ARM_PT_ArmoryPlayerPanel(bpy.types.Panel): 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") - layout.prop(wrd, 'arm_runtime') - layout.prop(wrd, 'arm_play_camera') - layout.prop(wrd, 'arm_play_scene') + 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() - # Less spacing between lines + box.alert = True + col = box.column(align=True) col.label(text=f'{log.num_warnings} warnings occurred during compilation!', icon='ERROR') # Blank icon to achieve the same indentation as the line before From 22d3530863e659e81f4df74494d7a9c1b9a2b816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 21 Jan 2021 16:12:32 +0100 Subject: [PATCH 029/264] Blender 2.9: Add Nishita sky model implementation --- Shaders/std/sky.glsl | 118 ++++++++++++++++++ .../material/cycles_nodes/nodes_texture.py | 27 +++- 2 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 Shaders/std/sky.glsl diff --git a/Shaders/std/sky.glsl b/Shaders/std/sky.glsl new file mode 100644 index 00000000..21c64ea0 --- /dev/null +++ b/Shaders/std/sky.glsl @@ -0,0 +1,118 @@ +/* Various sky functions + * + * Nishita model is based on https://github.com/wwwtyro/glsl-atmosphere (Unlicense License) + * Changes to the original implementation: + * - r and pSun parameters of nishita_atmosphere() are already normalized + */ + +#ifndef _SKY_GLSL_ +#define _SKY_GLSL_ + +#define PI 3.141592 + +#define nishita_iSteps 16 +#define nishita_jSteps 8 + +/* ray-sphere intersection that assumes + * the sphere is centered at the origin. + * No intersection when result.x > result.y */ +vec2 nishita_rsi(const vec3 r0, const vec3 rd, const float sr) { + float a = dot(rd, rd); + float b = 2.0 * dot(rd, r0); + float c = dot(r0, r0) - (sr * sr); + float d = (b*b) - 4.0*a*c; + + if (d < 0.0) return vec2(1e5,-1e5); + return vec2( + (-b - sqrt(d))/(2.0*a), + (-b + sqrt(d))/(2.0*a) + ); +} + +vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const float iSun, const float rPlanet, const float rAtmos, const vec3 kRlh, const float kMie, const float shRlh, const float shMie, const float g) { + // r and pSun must be already normalized! + + // Calculate the step size of the primary ray. + vec2 p = nishita_rsi(r0, r, rAtmos); + if (p.x > p.y) return vec3(0,0,0); + p.y = min(p.y, nishita_rsi(r0, r, rPlanet).x); + float iStepSize = (p.y - p.x) / float(nishita_iSteps); + + // Initialize the primary ray time. + float iTime = 0.0; + + // Initialize accumulators for Rayleigh and Mie scattering. + vec3 totalRlh = vec3(0,0,0); + vec3 totalMie = vec3(0,0,0); + + // Initialize optical depth accumulators for the primary ray. + float iOdRlh = 0.0; + float iOdMie = 0.0; + + // Calculate the Rayleigh and Mie phases. + float mu = dot(r, pSun); + float mumu = mu * mu; + float gg = g * g; + float pRlh = 3.0 / (16.0 * PI) * (1.0 + mumu); + float pMie = 3.0 / (8.0 * PI) * ((1.0 - gg) * (mumu + 1.0)) / (pow(1.0 + gg - 2.0 * mu * g, 1.5) * (2.0 + gg)); + + // Sample the primary ray. + for (int i = 0; i < nishita_iSteps; i++) { + + // Calculate the primary ray sample position. + vec3 iPos = r0 + r * (iTime + iStepSize * 0.5); + + // Calculate the height of the sample. + float iHeight = length(iPos) - rPlanet; + + // Calculate the optical depth of the Rayleigh and Mie scattering for this step. + float odStepRlh = exp(-iHeight / shRlh) * iStepSize; + float odStepMie = exp(-iHeight / shMie) * iStepSize; + + // Accumulate optical depth. + iOdRlh += odStepRlh; + iOdMie += odStepMie; + + // Calculate the step size of the secondary ray. + float jStepSize = nishita_rsi(iPos, pSun, rAtmos).y / float(nishita_jSteps); + + // Initialize the secondary ray time. + float jTime = 0.0; + + // Initialize optical depth accumulators for the secondary ray. + float jOdRlh = 0.0; + float jOdMie = 0.0; + + // Sample the secondary ray. + for (int j = 0; j < nishita_jSteps; j++) { + + // Calculate the secondary ray sample position. + vec3 jPos = iPos + pSun * (jTime + jStepSize * 0.5); + + // Calculate the height of the sample. + float jHeight = length(jPos) - rPlanet; + + // Accumulate the optical depth. + jOdRlh += exp(-jHeight / shRlh) * jStepSize; + jOdMie += exp(-jHeight / shMie) * jStepSize; + + // Increment the secondary ray time. + jTime += jStepSize; + } + + // Calculate attenuation. + vec3 attn = exp(-(kMie * (iOdMie + jOdMie) + kRlh * (iOdRlh + jOdRlh))); + + // Accumulate scattering. + totalRlh += odStepRlh * attn; + totalMie += odStepMie * attn; + + // Increment the primary ray time. + iTime += iStepSize; + } + + // Calculate and return the final color. + return iSun * (pRlh * kRlh * totalRlh + pMie * kMie * totalMie); +} + +#endif diff --git a/blender/arm/material/cycles_nodes/nodes_texture.py b/blender/arm/material/cycles_nodes/nodes_texture.py index b9f57daf..f3f9c070 100644 --- a/blender/arm/material/cycles_nodes/nodes_texture.py +++ b/blender/arm/material/cycles_nodes/nodes_texture.py @@ -293,6 +293,21 @@ def parse_tex_sky(node: bpy.types.ShaderNodeTexSky, out_socket: bpy.types.NodeSo # Pass through return c.to_vec3([0.0, 0.0, 0.0]) + if node.sky_type == 'PREETHAM' or node.sky_type == 'HOSEK_WILKIE': + if node.sky_type == 'PREETHAM': + log.warn('Preetham sky model is not supported, using Hosek Wilkie sky model instead') + + return parse_sky_hosekwilkie(node, state) + + elif node.sky_type == 'NISHITA': + return parse_sky_nishita(node, state) + + else: + log.error(f'Unsupported sky model: {node.sky_type}!') + return c.to_vec3([0.0, 0.0, 0.0]) + + +def parse_sky_hosekwilkie(node: bpy.types.ShaderNodeTexSky, state: ParserState) -> vec3str: world = state.world curshader = state.curshader @@ -312,10 +327,10 @@ def parse_tex_sky(node: bpy.types.ShaderNodeTexSky, out_socket: bpy.types.NodeSo curshader.add_uniform('vec3 I', link="_hosekI") curshader.add_uniform('vec3 Z', link="_hosekZ") curshader.add_uniform('vec3 hosekSunDirection', link="_hosekSunDirection") - curshader.add_function('''vec3 hosekWilkie(float cos_theta, float gamma, float cos_gamma) { + curshader.add_function("""vec3 hosekWilkie(float cos_theta, float gamma, float cos_gamma) { \tvec3 chi = (1 + cos_gamma * cos_gamma) / pow(1 + H * H - 2 * cos_gamma * H, vec3(1.5)); \treturn (1 + A * exp(B / (cos_theta + 0.01))) * (C + D * exp(E * gamma) + F * (cos_gamma * cos_gamma) + G * chi + I * sqrt(cos_theta)); -}''') +}""") world.arm_envtex_sun_direction = [node.sun_direction[0], node.sun_direction[1], node.sun_direction[2]] world.arm_envtex_turbidity = node.turbidity @@ -353,6 +368,14 @@ def parse_tex_sky(node: bpy.types.ShaderNodeTexSky, out_socket: bpy.types.NodeSo return 'Z * hosekWilkie(cos_theta, gamma_val, cos_gamma) * envmapStrength;' +def parse_sky_nishita(node: bpy.types.ShaderNodeTexSky, state: ParserState) -> vec3str: + curshader = state.curshader + curshader.add_include('std/sky.glsl') + curshader.add_uniform('vec3 sunDir', link='_sunDirection') + + return 'nishita_atmosphere(n, vec3(0,0,6372e3), sunDir, 22.0, 6371e3, 6471e3, vec3(5.5e-6,13.0e-6,22.4e-6), 21e-6, 8e3, 1.2e3, 0.758)' + + def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: if state.context == ParserContext.OBJECT: log.warn('Environment Texture node is not supported for object node trees, using default value') From 396e60574ad2d17b8e1012e66a5994767d4c6b0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 21 Jan 2021 20:36:03 +0100 Subject: [PATCH 030/264] Optimize Nishita sky shader --- Shaders/std/sky.glsl | 42 +++++++++++++------ .../material/cycles_nodes/nodes_texture.py | 5 ++- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/Shaders/std/sky.glsl b/Shaders/std/sky.glsl index 21c64ea0..310ed2ad 100644 --- a/Shaders/std/sky.glsl +++ b/Shaders/std/sky.glsl @@ -3,6 +3,7 @@ * Nishita model is based on https://github.com/wwwtyro/glsl-atmosphere (Unlicense License) * Changes to the original implementation: * - r and pSun parameters of nishita_atmosphere() are already normalized + * - Some original parameters of nishita_atmosphere() are replaced with pre-defined values */ #ifndef _SKY_GLSL_ @@ -13,6 +14,18 @@ #define nishita_iSteps 16 #define nishita_jSteps 8 +// The values here are taken from Cycles code if they +// exist there, otherwise they are taken from the example +// in the glsl-atmosphere repo +#define nishita_sun_intensity 22.0 +#define nishita_atmo_radius 6420e3 +#define nishita_rayleigh_scale 8e3 +#define nishita_rayleigh_coeff vec3(5.5e-6, 13.0e-6, 22.4e-6) +#define nishita_mie_scale 1.2e3 +#define nishita_mie_coeff 2e-5 +#define nishita_mie_dir 0.76 // Aerosols anisotropy ("direction") +#define nishita_mie_dir_sq 0.5776 // Squared aerosols anisotropy + /* ray-sphere intersection that assumes * the sphere is centered at the origin. * No intersection when result.x > result.y */ @@ -29,11 +42,15 @@ vec2 nishita_rsi(const vec3 r0, const vec3 rd, const float sr) { ); } -vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const float iSun, const float rPlanet, const float rAtmos, const vec3 kRlh, const float kMie, const float shRlh, const float shMie, const float g) { - // r and pSun must be already normalized! - +/* + * r: normalized ray direction + * r0: ray origin + * pSun: normalized sun direction + * rPlanet: planet radius + */ +vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const float rPlanet) { // Calculate the step size of the primary ray. - vec2 p = nishita_rsi(r0, r, rAtmos); + vec2 p = nishita_rsi(r0, r, nishita_atmo_radius); if (p.x > p.y) return vec3(0,0,0); p.y = min(p.y, nishita_rsi(r0, r, rPlanet).x); float iStepSize = (p.y - p.x) / float(nishita_iSteps); @@ -52,9 +69,8 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa // Calculate the Rayleigh and Mie phases. float mu = dot(r, pSun); float mumu = mu * mu; - float gg = g * g; float pRlh = 3.0 / (16.0 * PI) * (1.0 + mumu); - float pMie = 3.0 / (8.0 * PI) * ((1.0 - gg) * (mumu + 1.0)) / (pow(1.0 + gg - 2.0 * mu * g, 1.5) * (2.0 + gg)); + float pMie = 3.0 / (8.0 * PI) * ((1.0 - nishita_mie_dir_sq) * (mumu + 1.0)) / (pow(1.0 + nishita_mie_dir_sq - 2.0 * mu * nishita_mie_dir, 1.5) * (2.0 + nishita_mie_dir_sq)); // Sample the primary ray. for (int i = 0; i < nishita_iSteps; i++) { @@ -66,15 +82,15 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa float iHeight = length(iPos) - rPlanet; // Calculate the optical depth of the Rayleigh and Mie scattering for this step. - float odStepRlh = exp(-iHeight / shRlh) * iStepSize; - float odStepMie = exp(-iHeight / shMie) * iStepSize; + float odStepRlh = exp(-iHeight / nishita_rayleigh_scale) * iStepSize; + float odStepMie = exp(-iHeight / nishita_mie_scale) * iStepSize; // Accumulate optical depth. iOdRlh += odStepRlh; iOdMie += odStepMie; // Calculate the step size of the secondary ray. - float jStepSize = nishita_rsi(iPos, pSun, rAtmos).y / float(nishita_jSteps); + float jStepSize = nishita_rsi(iPos, pSun, nishita_atmo_radius).y / float(nishita_jSteps); // Initialize the secondary ray time. float jTime = 0.0; @@ -93,15 +109,15 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa float jHeight = length(jPos) - rPlanet; // Accumulate the optical depth. - jOdRlh += exp(-jHeight / shRlh) * jStepSize; - jOdMie += exp(-jHeight / shMie) * jStepSize; + jOdRlh += exp(-jHeight / nishita_rayleigh_scale) * jStepSize; + jOdMie += exp(-jHeight / nishita_mie_scale) * jStepSize; // Increment the secondary ray time. jTime += jStepSize; } // Calculate attenuation. - vec3 attn = exp(-(kMie * (iOdMie + jOdMie) + kRlh * (iOdRlh + jOdRlh))); + vec3 attn = exp(-(nishita_mie_coeff * (iOdMie + jOdMie) + nishita_rayleigh_coeff * (iOdRlh + jOdRlh))); // Accumulate scattering. totalRlh += odStepRlh * attn; @@ -112,7 +128,7 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa } // Calculate and return the final color. - return iSun * (pRlh * kRlh * totalRlh + pMie * kMie * totalMie); + return nishita_sun_intensity * (pRlh * nishita_rayleigh_coeff * totalRlh + pMie * nishita_mie_coeff * totalMie); } #endif diff --git a/blender/arm/material/cycles_nodes/nodes_texture.py b/blender/arm/material/cycles_nodes/nodes_texture.py index f3f9c070..1cb4eb98 100644 --- a/blender/arm/material/cycles_nodes/nodes_texture.py +++ b/blender/arm/material/cycles_nodes/nodes_texture.py @@ -373,7 +373,10 @@ def parse_sky_nishita(node: bpy.types.ShaderNodeTexSky, state: ParserState) -> v curshader.add_include('std/sky.glsl') curshader.add_uniform('vec3 sunDir', link='_sunDirection') - return 'nishita_atmosphere(n, vec3(0,0,6372e3), sunDir, 22.0, 6371e3, 6471e3, vec3(5.5e-6,13.0e-6,22.4e-6), 21e-6, 8e3, 1.2e3, 0.758)' + planet_radius = 6360e3 # Earth radius used in Blender + ray_origin_z = planet_radius + node.altitude * 1000 + + return f'nishita_atmosphere(n, vec3(0, 0, {ray_origin_z}), sunDir, {planet_radius})' def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: From 288ead64dc8e657078cd7848591d7d91dd8e3dba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 21 Jan 2021 21:14:05 +0100 Subject: [PATCH 031/264] Nishita sky: implement air and dust density --- Shaders/std/sky.glsl | 22 +++++++++++-------- blender/arm/material/cycles.py | 6 ++++- .../material/cycles_nodes/nodes_texture.py | 8 ++++++- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/Shaders/std/sky.glsl b/Shaders/std/sky.glsl index 310ed2ad..921eb7e8 100644 --- a/Shaders/std/sky.glsl +++ b/Shaders/std/sky.glsl @@ -1,9 +1,12 @@ /* Various sky functions + * ===================== * - * Nishita model is based on https://github.com/wwwtyro/glsl-atmosphere (Unlicense License) - * Changes to the original implementation: - * - r and pSun parameters of nishita_atmosphere() are already normalized - * - Some original parameters of nishita_atmosphere() are replaced with pre-defined values + * Nishita model is based on https://github.com/wwwtyro/glsl-atmosphere(Unlicense License) + * + * Changes to the original implementation: + * - r and pSun parameters of nishita_atmosphere() are already normalized + * - Some original parameters of nishita_atmosphere() are replaced with pre-defined values + * - Implemented air and dust density node parameters (see Blender source) */ #ifndef _SKY_GLSL_ @@ -47,8 +50,9 @@ vec2 nishita_rsi(const vec3 r0, const vec3 rd, const float sr) { * r0: ray origin * pSun: normalized sun direction * rPlanet: planet radius + * density: (air density, dust density) */ -vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const float rPlanet) { +vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const float rPlanet, const vec2 density) { // Calculate the step size of the primary ray. vec2 p = nishita_rsi(r0, r, nishita_atmo_radius); if (p.x > p.y) return vec3(0,0,0); @@ -82,8 +86,8 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa float iHeight = length(iPos) - rPlanet; // Calculate the optical depth of the Rayleigh and Mie scattering for this step. - float odStepRlh = exp(-iHeight / nishita_rayleigh_scale) * iStepSize; - float odStepMie = exp(-iHeight / nishita_mie_scale) * iStepSize; + float odStepRlh = exp(-iHeight / nishita_rayleigh_scale) * density.x * iStepSize; + float odStepMie = exp(-iHeight / nishita_mie_scale) * density.y * iStepSize; // Accumulate optical depth. iOdRlh += odStepRlh; @@ -109,8 +113,8 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa float jHeight = length(jPos) - rPlanet; // Accumulate the optical depth. - jOdRlh += exp(-jHeight / nishita_rayleigh_scale) * jStepSize; - jOdMie += exp(-jHeight / nishita_mie_scale) * jStepSize; + jOdRlh += exp(-jHeight / nishita_rayleigh_scale) * density.x * jStepSize; + jOdMie += exp(-jHeight / nishita_mie_scale) * density.y * jStepSize; // Increment the secondary ray time. jTime += jStepSize; diff --git a/blender/arm/material/cycles.py b/blender/arm/material/cycles.py index 71cf6f84..b587226c 100644 --- a/blender/arm/material/cycles.py +++ b/blender/arm/material/cycles.py @@ -640,8 +640,12 @@ def to_vec1(v): return str(v) +def to_vec2(v): + return f'vec2({v[0]}, {v[1]})' + + def to_vec3(v): - return 'vec3({0}, {1}, {2})'.format(v[0], v[1], v[2]) + return f'vec3({v[0]}, {v[1]}, {v[2]})' def rgb_to_bw(res_var: vec3str) -> floatstr: diff --git a/blender/arm/material/cycles_nodes/nodes_texture.py b/blender/arm/material/cycles_nodes/nodes_texture.py index 1cb4eb98..4aef494c 100644 --- a/blender/arm/material/cycles_nodes/nodes_texture.py +++ b/blender/arm/material/cycles_nodes/nodes_texture.py @@ -376,7 +376,13 @@ def parse_sky_nishita(node: bpy.types.ShaderNodeTexSky, state: ParserState) -> v planet_radius = 6360e3 # Earth radius used in Blender ray_origin_z = planet_radius + node.altitude * 1000 - return f'nishita_atmosphere(n, vec3(0, 0, {ray_origin_z}), sunDir, {planet_radius})' + d_air = node.air_density + d_dust = node.dust_density + # Todo: Implement ozone density (ignored for now) + # d_ozone = node.ozone_density + density = c.to_vec2((d_air, d_dust)) + + return f'nishita_atmosphere(n, vec3(0, 0, {ray_origin_z}), sunDir, {planet_radius}, {density})' def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: From 6b6dc6264fd235bfc8b801978acbca13efb761b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sun, 24 Jan 2021 20:42:18 +0100 Subject: [PATCH 032/264] Blender 2.9: Improve exporter settings UI --- blender/arm/props.py | 2 +- blender/arm/props_ui.py | 185 ++++++++++++++++++---------------------- 2 files changed, 86 insertions(+), 101 deletions(-) diff --git a/blender/arm/props.py b/blender/arm/props.py index ab91f129..e9c6b183 100755 --- a/blender/arm/props.py +++ b/blender/arm/props.py @@ -178,7 +178,7 @@ def init_properties(): ('ErrorsOnly', 'Errors Only', 'Show only errors')], name="Compile Log Parameter", update=assets.invalidate_compiler_cache, default="Summary") - bpy.types.World.arm_project_win_build_cpu = IntProperty(name="Count CPU", description="Specifies the maximum number of concurrent processes to use when building", default=1, min=1, max=multiprocessing.cpu_count()) + bpy.types.World.arm_project_win_build_cpu = IntProperty(name="CPU Count", description="Specifies the maximum number of concurrent processes to use when building", default=1, min=1, max=multiprocessing.cpu_count()) bpy.types.World.arm_project_win_build_open = BoolProperty(name="Open Build Directory", description="Open the build directory after successfully assemble", default=False) bpy.types.World.arm_project_icon = StringProperty(name="Icon (PNG)", description="Exported project icon, must be a PNG image", default="", subtype="FILE_PATH", update=assets.invalidate_compiler_cache) diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index fc31a31d..a767aba7 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -494,45 +494,55 @@ class ARM_PT_ArmoryExporterPanel(bpy.types.Panel): col.prop(wrd, 'arm_asset_compression') col.prop(wrd, 'arm_single_data_file') -class ARM_PT_ArmoryExporterAndroidSettingsPanel(bpy.types.Panel): - bl_label = "Android Settings" - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "render" - bl_options = { 'HIDE_HEADER' } - bl_parent_id = "ARM_PT_ArmoryExporterPanel" + +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 == 'android-hl' - else: - return False + 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'] - # Options - layout.label(text='Android Settings', icon='SETTINGS') - row = layout.row() - row.prop(wrd, 'arm_winorient') - row = layout.row() - row.prop(wrd, 'arm_project_android_sdk_compile') - row = layout.row() - row.prop(wrd, 'arm_project_android_sdk_min') - row = layout.row() - row.prop(wrd, 'arm_project_android_sdk_target') + + 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_options = {'DEFAULT_CLOSED'} bl_parent_id = "ARM_PT_ArmoryExporterAndroidSettingsPanel" def draw(self, context): @@ -590,7 +600,7 @@ class ARM_PT_ArmoryExporterAndroidBuildAPKPanel(bpy.types.Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "render" - bl_options = { 'DEFAULT_CLOSED'} + bl_options = {'DEFAULT_CLOSED'} bl_parent_id = "ARM_PT_ArmoryExporterAndroidSettingsPanel" def draw(self, context): @@ -598,109 +608,84 @@ class ARM_PT_ArmoryExporterAndroidBuildAPKPanel(bpy.types.Panel): layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] - row = layout.row() - row.prop(wrd, 'arm_project_android_build_apk') path = arm.utils.get_android_sdk_root_path() + + col = layout.column() + + row = col.row() row.enabled = len(path) > 0 - row = layout.row() - row.prop(wrd, 'arm_project_android_rename_apk') + row.prop(wrd, 'arm_project_android_build_apk') + + row = col.row() row.enabled = wrd.arm_project_android_build_apk - row = layout.row() + 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.enabled = (wrd.arm_project_android_build_apk) and (len(arm.utils.get_android_apk_copy_path()) > 0) - row = layout.row() + + row = col.row(align=True) row.prop(wrd, 'arm_project_android_list_avd') - col = row.column(align=True) - col.operator('arm.update_list_android_emulator', text='', icon='FILE_REFRESH') - col.enabled = len(path) > 0 - col = row.column(align=True) - col.operator('arm.run_android_emulator', text='', icon='PLAY') - col.enabled = len(path) > 0 and len(arm.utils.get_android_emulator_name()) > 0 - row = layout.row() - row.prop(wrd, 'arm_project_android_run_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(bpy.types.Panel): + +class ARM_PT_ArmoryExporterHTML5SettingsPanel(ExporterTargetSettingsMixin, bpy.types.Panel): bl_label = "HTML5 Settings" - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "render" - bl_options = { 'HIDE_HEADER' } - bl_parent_id = "ARM_PT_ArmoryExporterPanel" - - @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 == 'html5' - else: - return False + 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'] - # Options - layout.label(text='HTML5 Settings', icon='SETTINGS') - row = layout.row() - row.prop(wrd, 'arm_project_html5_popupmenu_in_browser') - row = layout.row() - row.prop(wrd, 'arm_project_html5_copy') + + 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 = layout.row() + 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') - 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) -class ARM_PT_ArmoryExporterWindowsSettingsPanel(bpy.types.Panel): + +class ARM_PT_ArmoryExporterWindowsSettingsPanel(ExporterTargetSettingsMixin, bpy.types.Panel): bl_label = "Windows Settings" - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "render" - bl_options = { 'HIDE_HEADER' } - bl_parent_id = "ARM_PT_ArmoryExporterPanel" - - @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 == 'windows-hl' - else: - return False + 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'] - # Options - layout.label(text='Windows Settings', icon='SETTINGS') - row = layout.row() + + col = layout.column() + row = col.row(align=True) row.prop(wrd, 'arm_project_win_list_vs') - col = row.column(align=True) - col.operator('arm.update_list_installed_vs', text='', icon='FILE_REFRESH') - col.enabled = arm.utils.get_os_is_windows() - row = layout.row() - row.prop(wrd, 'arm_project_win_build') + 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() - is_enable = arm.utils.get_os_is_windows() and wrd.arm_project_win_build != '0' and wrd.arm_project_win_build != '1' - row = layout.row() - row.prop(wrd, 'arm_project_win_build_mode') - row.enabled = is_enable - row = layout.row() - row.prop(wrd, 'arm_project_win_build_arch') - row.enabled = is_enable - row = layout.row() - row.prop(wrd, 'arm_project_win_build_log') - row.enabled = is_enable - row = layout.row() - row.prop(wrd, 'arm_project_win_build_cpu') - row.enabled = is_enable - row = layout.row() - row.prop(wrd, 'arm_project_win_build_open') - row.enabled = is_enable + 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" @@ -2737,4 +2722,4 @@ def unregister(): bpy.utils.unregister_class(ArmProxyApplyAllButton) bpy.utils.unregister_class(ArmSyncProxyButton) bpy.utils.unregister_class(ArmPrintTraitsButton) - bpy.utils.unregister_class(ARM_PT_MaterialNodePanel) \ No newline at end of file + bpy.utils.unregister_class(ARM_PT_MaterialNodePanel) From 6ae76bfdf6fed53f8c593dd8a780aa9dc4e7ac62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Mon, 25 Jan 2021 13:53:28 +0100 Subject: [PATCH 033/264] Blender 2.9: Improve exporter panel UI --- blender/arm/props_ui.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index a767aba7..4179e939 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -442,12 +442,14 @@ class ARM_PT_ArmoryExporterPanel(bpy.types.Panel): layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] + row = layout.row(align=True) row.alignment = 'EXPAND' - row.operator("arm.build_project") + row.scale_y = 1.3 + row.enabled = wrd.arm_exporterlist_index >= 0 and len(wrd.arm_exporterlist) > 0 + row.operator("arm.build_project", icon="MOD_BUILD") # row.operator("arm.patch_project") row.operator("arm.publish_project", icon="EXPORT") - row.enabled = wrd.arm_exporterlist_index >= 0 and len(wrd.arm_exporterlist) > 0 rows = 2 if len(wrd.arm_exporterlist) > 1: @@ -480,16 +482,24 @@ class ARM_PT_ArmoryExporterPanel(bpy.types.Panel): box.prop_search(item, 'arm_project_scene', bpy.data, 'scenes', text='Scene') layout.separator() - col = layout.column() + 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_optimize_data') col.prop(wrd, 'arm_asset_compression') col.prop(wrd, 'arm_single_data_file') From d7e70c4c0a0f25d41e79550db15c613cab99802b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Mon, 25 Jan 2021 13:58:47 +0100 Subject: [PATCH 034/264] Blender 2.9: Improve project flags panel UI --- blender/arm/props.py | 4 ++-- blender/arm/props_ui.py | 34 +++++++++++++++++++++------------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/blender/arm/props.py b/blender/arm/props.py index e9c6b183..16693a93 100755 --- a/blender/arm/props.py +++ b/blender/arm/props.py @@ -212,11 +212,11 @@ def init_properties(): bpy.types.World.arm_khafile = PointerProperty(name="Khafile", description="Source appended to khafile.js", update=assets.invalidate_compiler_cache, type=bpy.types.Text) bpy.types.World.arm_texture_quality = FloatProperty(name="Texture Quality", default=1.0, min=0.0, max=1.0, subtype='FACTOR', update=assets.invalidate_compiler_cache) bpy.types.World.arm_sound_quality = FloatProperty(name="Sound Quality", default=0.9, min=0.0, max=1.0, subtype='FACTOR', update=assets.invalidate_compiler_cache) - bpy.types.World.arm_minimize = BoolProperty(name="Minimize Data", description="Export scene data in binary", default=True, update=assets.invalidate_compiled_data) + bpy.types.World.arm_minimize = BoolProperty(name="Binary Scene Data", description="Export scene data in binary", default=True, update=assets.invalidate_compiled_data) bpy.types.World.arm_minify_js = BoolProperty(name="Minify JS", description="Minimize JavaScript output when publishing", default=True) bpy.types.World.arm_optimize_data = BoolProperty(name="Optimize Data", description="Export more efficient geometry and shader data, prolongs build times", default=True, update=assets.invalidate_compiled_data) bpy.types.World.arm_deinterleaved_buffers = BoolProperty(name="Deinterleaved Buffers", description="Use deinterleaved vertex buffers", default=False, update=assets.invalidate_compiler_cache) - bpy.types.World.arm_export_tangents = BoolProperty(name="Export Tangents", description="Precompute tangents for normal mapping, otherwise computed in shader", default=True, update=assets.invalidate_compiled_data) + bpy.types.World.arm_export_tangents = BoolProperty(name="Precompute Tangents", description="Precompute tangents for normal mapping, otherwise computed in shader", default=True, update=assets.invalidate_compiled_data) bpy.types.World.arm_batch_meshes = BoolProperty(name="Batch Meshes", description="Group meshes by materials to speed up rendering", default=False, update=assets.invalidate_compiler_cache) bpy.types.World.arm_batch_materials = BoolProperty(name="Batch Materials", description="Marge similar materials into single pipeline state", default=False, update=assets.invalidate_shader_cache) bpy.types.World.arm_stream_scene = BoolProperty(name="Stream Scene", description="Stream scene content", default=False, update=assets.invalidate_compiler_cache) diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index 4179e939..3ca9a6e2 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -500,6 +500,7 @@ class ARM_PT_ArmoryExporterPanel(bpy.types.Panel): 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') @@ -724,19 +725,26 @@ class ARM_PT_ProjectFlagsPanel(bpy.types.Panel): layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] - layout.prop(wrd, 'arm_verbose_output') - layout.prop(wrd, 'arm_cache_build') - layout.prop(wrd, 'arm_live_patch') - layout.prop(wrd, 'arm_stream_scene') - layout.prop(wrd, 'arm_batch_meshes') - layout.prop(wrd, 'arm_batch_materials') - layout.prop(wrd, 'arm_write_config') - layout.prop(wrd, 'arm_minimize') - layout.prop(wrd, 'arm_deinterleaved_buffers') - layout.prop(wrd, 'arm_export_tangents') - layout.prop(wrd, 'arm_loadscreen') - layout.prop(wrd, 'arm_texture_quality') - layout.prop(wrd, 'arm_sound_quality') + + 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" From ba62ba0285e4137c518931389034ceddc1dfc6e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Mon, 25 Jan 2021 14:01:53 +0100 Subject: [PATCH 035/264] Blender 2.9: Fix/Improve collision filter mask UI --- blender/arm/props_collision_filter_mask.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/blender/arm/props_collision_filter_mask.py b/blender/arm/props_collision_filter_mask.py index d5b52910..6c4b93b5 100644 --- a/blender/arm/props_collision_filter_mask.py +++ b/blender/arm/props_collision_filter_mask.py @@ -1,31 +1,31 @@ import bpy -from bpy.props import * -from bpy.types import Panel + class ARM_PT_RbCollisionFilterMaskPanel(bpy.types.Panel): bl_label = "Armory Collision Filter Mask" 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_split = False layout.use_property_decorate = False obj = bpy.context.object - if obj == None: + if obj is None: return - if obj.rigid_body != None: + if obj.rigid_body is not None: layout.prop(obj, 'arm_rb_collision_filter_mask') + def register(): bpy.utils.register_class(ARM_PT_RbCollisionFilterMaskPanel) bpy.types.Object.arm_rb_collision_filter_mask = bpy.props.BoolVectorProperty( name="Collision Filter Mask", - default=(True, False, False,False,False,False, False, False,False,False,False, False, False,False,False,False, False, False,False,False), + default=[True] + [False] * 19, size=20, subtype='LAYER') + def unregister(): bpy.utils.unregister_class(ARM_PT_RbCollisionFilterMaskPanel) From 1d79708b22d1ba700038eab011d0406a6b24e6dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Mon, 25 Jan 2021 16:23:03 +0100 Subject: [PATCH 036/264] Fix trait type backward compatibility with pre-Blender2.9 files --- blender/arm/props_traits.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/blender/arm/props_traits.py b/blender/arm/props_traits.py index e10bf283..8e3ddbc0 100755 --- a/blender/arm/props_traits.py +++ b/blender/arm/props_traits.py @@ -21,12 +21,15 @@ 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, 1), + ('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, 4) + ('WebAssembly', 'Wasm', 'WebAssembly', ICON_WASM, 1) ] From d55f889a8408cb39a4a372d4324b97e0f8bed0b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Mon, 25 Jan 2021 16:45:30 +0100 Subject: [PATCH 037/264] Another small compositor panel UI improvement --- blender/arm/props_ui.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index 3ca9a6e2..d216a027 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -1383,6 +1383,7 @@ class ARM_PT_RenderPathCompositorPanel(bpy.types.Panel): 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') @@ -1411,6 +1412,7 @@ class ARM_PT_RenderPathCompositorPanel(bpy.types.Panel): col = layout.column() col.prop(rpdat, 'arm_lensflare') col.prop(rpdat, 'arm_fisheye') + layout.separator() col = layout.column() col.prop(rpdat, 'arm_lens_texture') From 05307817ee05c31c96f4673604462f32a04fea11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Mon, 25 Jan 2021 16:47:51 +0100 Subject: [PATCH 038/264] Fix grammar in warnings report --- blender/arm/make.py | 2 +- blender/arm/props_ui.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/blender/arm/make.py b/blender/arm/make.py index 0700afed..fe1e7f8c 100755 --- a/blender/arm/make.py +++ b/blender/arm/make.py @@ -433,7 +433,7 @@ def compilation_server_done(): def build_done(): print('Finished in ' + str(time.time() - profile_time)) if log.num_warnings > 0: - log.print_warn(f'{log.num_warnings} warnings occurred during compilation') + log.print_warn(f'{log.num_warnings} warning{"s" if log.num_warnings > 1 else ""} occurred during compilation') if state.proc_build is None: return result = state.proc_build.poll() diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index d216a027..32f3c719 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -426,7 +426,7 @@ class ARM_PT_ArmoryPlayerPanel(bpy.types.Panel): box.alert = True col = box.column(align=True) - col.label(text=f'{log.num_warnings} warnings occurred during compilation!', icon='ERROR') + col.label(text=f'{log.num_warnings} warning{"s" if log.num_warnings > 1 else ""} occurred during compilation!', icon='ERROR') # Blank icon to achieve the same indentation as the line before col.label(text='Please open the console to get more information.', icon='BLANK1') From e3f992b1f350b6149d579e20974997f20b8bf2ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Mon, 25 Jan 2021 16:48:03 +0100 Subject: [PATCH 039/264] Whitespace cleanup --- blender/arm/make.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/blender/arm/make.py b/blender/arm/make.py index fe1e7f8c..e2d8bff5 100755 --- a/blender/arm/make.py +++ b/blender/arm/make.py @@ -211,7 +211,7 @@ def export_data(fp, sdk_path): resx, resy = arm.utils.get_render_resolution(arm.utils.get_active_scene()) if wrd.arm_write_config: write_data.write_config(resx, resy) - + # Change project version (Build, Publish) if (not state.is_play) and (wrd.arm_project_version_autoinc): wrd.arm_project_version = arm.utils.arm.utils.change_version_project(wrd.arm_project_version) @@ -654,12 +654,12 @@ def build_success(): state.proc_publish_build = run_proc(cmd, done_gradlew_build) else: print('\nBuilding APK Warning: ANDROID_SDK_ROOT is not specified in environment variables and "Android SDK Path" setting is not specified in preferences: \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" in the preferences, then repeat operation "Publish"') - + # HTML5 After Publish if target_name.startswith('html5'): if len(arm.utils.get_html5_copy_path()) > 0 and (wrd.arm_project_html5_copy): project_name = arm.utils.safesrc(wrd.arm_project_name +'-'+ wrd.arm_project_version) - dst = os.path.join(arm.utils.get_html5_copy_path(), project_name) + dst = os.path.join(arm.utils.get_html5_copy_path(), project_name) if os.path.exists(dst): shutil.rmtree(dst) try: @@ -673,10 +673,10 @@ def build_success(): link_html5_app = arm.utils.get_link_web_server() +'/'+ project_name print("Running a browser with a link " + link_html5_app) webbrowser.open(link_html5_app) - + # Windows After Publish if target_name.startswith('windows'): - list_vs = [] + list_vs = [] err = '' # Print message project_name = arm.utils.safesrc(wrd.arm_project_name +'-'+ wrd.arm_project_version) @@ -707,18 +707,18 @@ def build_success(): for vs in list_vs: print('- ' + vs[1] + ' (version ' + vs[3] +')') return - # Current VS + # Current VS vs_path = '' for vs in list_vs: if vs[0] == wrd.arm_project_win_list_vs: vs_path = vs[2] break - # Open in Visual Studio + # Open in Visual Studio if int(wrd.arm_project_win_build) == 1: cmd = os.path.join('start "' + vs_path, 'Common7', 'IDE', 'devenv.exe" "' + os.path.join(project_path, project_name + '.sln"')) subprocess.Popen(cmd, shell=True) # Compile - if int(wrd.arm_project_win_build) > 1: + if int(wrd.arm_project_win_build) > 1: bits = '64' if wrd.arm_project_win_build_arch == 'x64' else '32' # vcvars cmd = os.path.join(vs_path, 'VC', 'Auxiliary', 'Build', 'vcvars' + bits + '.bat') @@ -792,7 +792,7 @@ def done_vs_vars(): # MSBuild wrd = bpy.data.worlds['Arm'] list_vs, err = arm.utils.get_list_installed_vs(True, True, True) - # Current VS + # Current VS vs_path = '' vs_name = '' for vs in list_vs: @@ -851,7 +851,7 @@ def done_vs_build(): os.chdir(res_path) # set work folder subprocess.Popen(cmd, shell=True) # Open Build Directory - if wrd.arm_project_win_build_open: + if wrd.arm_project_win_build_open: arm.utils.open_folder(path) state.redraw_ui = True else: From a1bbd76de74e7b72f83f5486b4732ca5eaaf9c0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Mon, 25 Jan 2021 16:51:28 +0100 Subject: [PATCH 040/264] Fix: Remove unused/non-existing armory icon --- blender/arm/ui_icons.py | 1 - 1 file changed, 1 deletion(-) diff --git a/blender/arm/ui_icons.py b/blender/arm/ui_icons.py index f9364dd7..ed2649f5 100644 --- a/blender/arm/ui_icons.py +++ b/blender/arm/ui_icons.py @@ -21,7 +21,6 @@ def _load_icons() -> None: bpy.utils.previews.remove(_icons_dict) _icons_dict = bpy.utils.previews.new() - _icons_dict.load("armory", os.path.join(_icons_dir, "armory.png"), 'IMAGE') _icons_dict.load("bundle", os.path.join(_icons_dir, "bundle.png"), 'IMAGE') _icons_dict.load("haxe", os.path.join(_icons_dir, "haxe.png"), 'IMAGE') _icons_dict.load("wasm", os.path.join(_icons_dir, "wasm.png"), 'IMAGE') From 75bb88831c85c887fdf766144bf15bb3f6af964d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Fri, 29 Jan 2021 21:38:14 +0100 Subject: [PATCH 041/264] Blender 2.9: Cycles attribute node - support alpha output and new attribute_type prop --- blender/arm/material/cycles.py | 35 ++++++++++++ .../arm/material/cycles_nodes/nodes_input.py | 54 +++++++++++-------- 2 files changed, 67 insertions(+), 22 deletions(-) diff --git a/blender/arm/material/cycles.py b/blender/arm/material/cycles.py index 71cf6f84..be92b3ce 100644 --- a/blender/arm/material/cycles.py +++ b/blender/arm/material/cycles.py @@ -644,6 +644,41 @@ def to_vec3(v): return 'vec3({0}, {1}, {2})'.format(v[0], v[1], v[2]) +def cast_value(val: str, from_type: str, to_type: str) -> str: + """Casts a value that is already parsed in a glsl string to another + value in a string. + + vec2 types are not supported (not used in the node editor) and there + is no cast towards int types. If casting from vec3 to vec4, the w + coordinate/alpha channel is filled with a 1. + + If this function is called with invalid parameters, a TypeError is + raised. + """ + if from_type == to_type: + return val + + if from_type in ('int', 'float'): + if to_type in ('int', 'float'): + return val + elif to_type in ('vec2', 'vec3', 'vec4'): + return f'{to_type}({val})' + + elif from_type == 'vec3': + if to_type == 'float': + return rgb_to_bw(val) + elif to_type == 'vec4': + return f'vec4({val}, 1.0)' + + elif from_type == 'vec4': + if to_type == 'float': + return rgb_to_bw(val) + elif to_type == 'vec3': + return f'{val}.xyz' + + raise TypeError("Invalid type cast in shader!") + + def rgb_to_bw(res_var: vec3str) -> floatstr: return f'((({res_var}.r * 0.3 + {res_var}.g * 0.59 + {res_var}.b * 0.11) / 3.0) * 2.5)' diff --git a/blender/arm/material/cycles_nodes/nodes_input.py b/blender/arm/material/cycles_nodes/nodes_input.py index 60d86799..69accb45 100644 --- a/blender/arm/material/cycles_nodes/nodes_input.py +++ b/blender/arm/material/cycles_nodes/nodes_input.py @@ -10,42 +10,52 @@ import arm.utils def parse_attribute(node: bpy.types.ShaderNodeAttribute, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]: - # Color - if out_socket == node.outputs[0]: - # Vertex colors only for now - state.con.add_elem('col', 'short4norm') - return 'vcolor' + out_type = 'float' if out_socket.type == 'VALUE' else 'vec3' - # Vector - elif out_socket == node.outputs[1]: - # UV maps only for now - state.con.add_elem('tex', 'short2norm') + if node.attribute_name == 'time': + state.curshader.add_uniform('float time', link='_time') + + if out_socket == node.outputs[3]: + return '1.0' + return c.cast_value('time', from_type='float', to_type=out_type) + + # UV maps (higher priority) and vertex colors + if node.attribute_type == 'GEOMETRY': + + # Alpha output. Armory doesn't support vertex colors with alpha + # values yet and UV maps don't have an alpha channel + if out_socket == node.outputs[3]: + return '1.0' + + # UV maps mat = c.mat_get_material() mat_users = c.mat_get_material_users() if mat_users is not None and mat in mat_users: mat_user = mat_users[mat][0] - # No UV layers for Curve + # Curves don't have uv layers, so check that first if hasattr(mat_user.data, 'uv_layers'): lays = mat_user.data.uv_layers + # First UV map referenced + if node.attribute_name == lays[0].name: + state.con.add_elem('tex', 'short2norm') + return c.cast_value('vec3(texCoord.x, 1.0 - texCoord.y, 0.0)', from_type='vec3', to_type=out_type) + # Second UV map referenced - if len(lays) > 1 and node.attribute_name == lays[1].name: + elif len(lays) > 1 and node.attribute_name == lays[1].name: state.con.add_elem('tex1', 'short2norm') - return 'vec3(texCoord1.x, 1.0 - texCoord1.y, 0.0)' + return c.cast_value('vec3(texCoord1.x, 1.0 - texCoord1.y, 0.0)', from_type='vec3', to_type=out_type) - return 'vec3(texCoord.x, 1.0 - texCoord.y, 0.0)' + # Vertex colors + # TODO: support multiple vertex color sets + state.con.add_elem('col', 'short4norm') + return c.cast_value('vcolor', from_type='vec3', to_type=out_type) - # Fac - else: - if node.attribute_name == 'time': - state.curshader.add_uniform('float time', link='_time') - return 'time' - - # Return 0.0 till drivers are implemented - else: - return '0.0' + if out_socket == node.outputs[3]: + return '1.0' + return c.cast_value('0.0', from_type='float', to_type=out_type) def parse_rgb(node: bpy.types.ShaderNodeRGB, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: From 121d449c35535586777fb4da19a4819eb8ffc242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Fri, 29 Jan 2021 22:15:21 +0100 Subject: [PATCH 042/264] Blender 2.9: Cycles attribute node - support object/custom properties --- .../arm/material/cycles_nodes/nodes_input.py | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/blender/arm/material/cycles_nodes/nodes_input.py b/blender/arm/material/cycles_nodes/nodes_input.py index 69accb45..2a79dffd 100644 --- a/blender/arm/material/cycles_nodes/nodes_input.py +++ b/blender/arm/material/cycles_nodes/nodes_input.py @@ -1,6 +1,8 @@ -import bpy from typing import Union +import bpy +import mathutils + import arm.log as log import arm.material.cycles as c import arm.material.cycles_functions as c_functions @@ -53,6 +55,35 @@ def parse_attribute(node: bpy.types.ShaderNodeAttribute, out_socket: bpy.types.N state.con.add_elem('col', 'short4norm') return c.cast_value('vcolor', from_type='vec3', to_type=out_type) + # Check object properties + # see https://developer.blender.org/rB6fdcca8de6 for reference + mat = c.mat_get_material() + mat_users = c.mat_get_material_users() + if mat_users is not None and mat in mat_users: + # Use first material user for now... + mat_user = mat_users[mat][0] + + val = None + # Custom properties first + if node.attribute_name in mat_user: + val = mat_user[node.attribute_name] + # Blender properties + elif hasattr(mat_user, node.attribute_name): + val = getattr(mat_user, node.attribute_name) + + if val is not None: + if isinstance(val, float): + return c.cast_value(str(val), from_type='float', to_type=out_type) + elif isinstance(val, int): + return c.cast_value(str(val), from_type='int', to_type=out_type) + elif isinstance(val, mathutils.Vector) and len(val) <= 4: + out = val.to_4d() + + if out_socket == node.outputs[3]: + return c.to_vec1(out[3]) + return c.cast_value(c.to_vec3(out), from_type='vec3', to_type=out_type) + + # Default values, attribute name did not match if out_socket == node.outputs[3]: return '1.0' return c.cast_value('0.0', from_type='float', to_type=out_type) From f2c16097d4a62e832b7f84d7a7bde121a2f8b2c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 30 Jan 2021 20:21:31 +0100 Subject: [PATCH 043/264] Fix lod operator polling and add some bl_options --- blender/arm/props_lod.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/blender/arm/props_lod.py b/blender/arm/props_lod.py index e1da2a82..44bfdca9 100755 --- a/blender/arm/props_lod.py +++ b/blender/arm/props_lod.py @@ -56,6 +56,7 @@ class ArmLodListNewItem(bpy.types.Operator): # Add a new item to the list bl_idname = "arm_lodlist.new_item" bl_label = "Add a new item" + bl_options = {'UNDO'} def execute(self, context): mdata = bpy.context.object.data @@ -68,10 +69,13 @@ class ArmLodListDeleteItem(bpy.types.Operator): # Delete the selected item from the list bl_idname = "arm_lodlist.delete_item" bl_label = "Deletes an item" + bl_options = {'INTERNAL', 'UNDO'} @classmethod - def poll(self, context): + def poll(cls, context): """ Enable if there's something in the list """ + if bpy.context.object is None: + return False mdata = bpy.context.object.data return len(mdata.arm_lodlist) > 0 @@ -97,6 +101,7 @@ class ArmLodListMoveItem(bpy.types.Operator): # Move an item in the list bl_idname = "arm_lodlist.move_item" bl_label = "Move an item in the list" + bl_options = {'INTERNAL', 'UNDO'} direction: EnumProperty( items=( ('UP', 'Up', ""), From 28011bcc009814df7558830fb9fd1c4aff5c56a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 13 Feb 2021 18:23:37 +0100 Subject: [PATCH 044/264] Nishita sky: add sun disk drawing --- Shaders/std/sky.glsl | 17 +++++++++++++++ .../material/cycles_nodes/nodes_texture.py | 21 ++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/Shaders/std/sky.glsl b/Shaders/std/sky.glsl index 921eb7e8..6b85b4cc 100644 --- a/Shaders/std/sky.glsl +++ b/Shaders/std/sky.glsl @@ -29,6 +29,8 @@ #define nishita_mie_dir 0.76 // Aerosols anisotropy ("direction") #define nishita_mie_dir_sq 0.5776 // Squared aerosols anisotropy +#define sun_limb_darkening_col vec3(0.397, 0.503, 0.652) + /* ray-sphere intersection that assumes * the sphere is centered at the origin. * No intersection when result.x > result.y */ @@ -135,4 +137,19 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa return nishita_sun_intensity * (pRlh * nishita_rayleigh_coeff * totalRlh + pMie * nishita_mie_coeff * totalMie); } +vec3 sun_disk(const vec3 n, const vec3 light_dir, const float disk_size, const float intensity) { + // Normalized SDF + float dist = distance(n, light_dir) / disk_size; + + // Darken the edges of the sun + // Reference: https://media.contentapi.ea.com/content/dam/eacom/frostbite/files/s2016-pbs-frostbite-sky-clouds-new.pdf + // (Physically Based Sky, Atmosphere and Cloud Rendering in Frostbite by Sebastien Hillaire) + // Page 28, Page 60 (Code from [Nec96]) + float invDist = 1.0 - dist; + float mu = sqrt(invDist * invDist); + vec3 limb_darkening = 1.0 - (1.0 - pow(vec3(mu), sun_limb_darkening_col)); + + return 1 + (1.0 - step(1.0, dist)) * nishita_sun_intensity * intensity * limb_darkening; +} + #endif diff --git a/blender/arm/material/cycles_nodes/nodes_texture.py b/blender/arm/material/cycles_nodes/nodes_texture.py index 4aef494c..b095785d 100644 --- a/blender/arm/material/cycles_nodes/nodes_texture.py +++ b/blender/arm/material/cycles_nodes/nodes_texture.py @@ -1,3 +1,4 @@ +import math import os from typing import Union @@ -382,7 +383,25 @@ def parse_sky_nishita(node: bpy.types.ShaderNodeTexSky, state: ParserState) -> v # d_ozone = node.ozone_density density = c.to_vec2((d_air, d_dust)) - return f'nishita_atmosphere(n, vec3(0, 0, {ray_origin_z}), sunDir, {planet_radius}, {density})' + sun = '' + if node.sun_disc: + # The sun size is calculated relative in terms of the distance + # between the sun position and the sky dome normal at every + # pixel (see sun_disk() in sky.glsl). + # + # An isosceles triangle is created with the camera at the + # opposite side of the base with node.sun_size being the vertex + # angle from which the base angle theta is calculated. Iron's + # skydome geometry roughly resembles a unit sphere, so the leg + # size is set to 1. The base size is the doubled normal-relative + # target size. + + # sun_size is already in radians despite being degrees in the UI + theta = 0.5 * (math.pi - node.sun_size) + size = math.cos(theta) + sun = f'* sun_disk(n, sunDir, {size}, {node.sun_intensity})' + + return f'nishita_atmosphere(n, vec3(0, 0, {ray_origin_z}), sunDir, {planet_radius}, {density}){sun}' def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: From 702436e2a10361e1bea124f8ed54281919de1c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 13 Feb 2021 19:01:14 +0100 Subject: [PATCH 045/264] Add artistic option for darkening clouds at night --- blender/arm/make_world.py | 16 +++++++++++++--- blender/arm/props.py | 4 ++++ blender/arm/props_ui.py | 1 + 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/blender/arm/make_world.py b/blender/arm/make_world.py index 8c3dcedd..98ac282c 100755 --- a/blender/arm/make_world.py +++ b/blender/arm/make_world.py @@ -293,7 +293,7 @@ def frag_write_clouds(world: bpy.types.World, frag: Shader): }''' frag.add_function(func_cloud_radiance) - frag.add_function('''vec3 traceClouds(vec3 sky, vec3 dir) { + func_trace_clouds = '''vec3 traceClouds(vec3 sky, vec3 dir) { \tconst float step_size = 0.5 / float(cloudsSteps); \tfloat T = 1.0; \tfloat C = 0.0; @@ -312,6 +312,16 @@ def frag_write_clouds(world: bpy.types.World, frag: Shader): \t\t} \t\tuv += (dir.xy / dir.z) * step_size * cloudsUpper; \t} +''' + if world.arm_darken_clouds: + func_trace_clouds += '\t// Darken clouds when the sun is low\n' -\treturn vec3(C) + sky * T; -}''') + # Nishita sky + if 'vec3 sunDir' in frag.uniforms: + func_trace_clouds += '\tC *= smoothstep(-0.02, 0.25, sunDir.z);\n' + # Hosek + else: + func_trace_clouds += '\tC *= smoothstep(0.04, 0.32, hosekSunDirection.z);\n' + + func_trace_clouds += '\treturn vec3(C) + sky * T;\n}' + frag.add_function(func_trace_clouds) diff --git a/blender/arm/props.py b/blender/arm/props.py index ae6aa0a9..1c5b1c5b 100755 --- a/blender/arm/props.py +++ b/blender/arm/props.py @@ -435,6 +435,10 @@ def init_properties(): bpy.types.World.compo_defs = StringProperty(name="Compositor Shader Defs", default='') bpy.types.World.arm_use_clouds = BoolProperty(name="Clouds", default=False, update=assets.invalidate_shader_cache) + bpy.types.World.arm_darken_clouds = BoolProperty( + name="Darken Clouds at Night", + description="Darkens the clouds when the sun is low. This setting is for artistic purposes and is not physically correct", + default=False, update=assets.invalidate_shader_cache) bpy.types.World.arm_clouds_lower = FloatProperty(name="Lower", default=1.0, min=0.1, max=10.0, update=assets.invalidate_shader_cache) bpy.types.World.arm_clouds_upper = FloatProperty(name="Upper", default=1.0, min=0.1, max=10.0, update=assets.invalidate_shader_cache) bpy.types.World.arm_clouds_wind = FloatVectorProperty(name="Wind", default=[1.0, 0.0], size=2, update=assets.invalidate_shader_cache) diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index 82b69bc4..64435781 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -244,6 +244,7 @@ class ARM_PT_WorldPropsPanel(bpy.types.Panel): 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') From 92554876f191265791892e77e4b085f47978cce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sun, 21 Feb 2021 01:12:15 +0100 Subject: [PATCH 046/264] Fix nishita sky altitude The scale was changed in recent Blender builds --- blender/arm/material/cycles_nodes/nodes_texture.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/material/cycles_nodes/nodes_texture.py b/blender/arm/material/cycles_nodes/nodes_texture.py index b095785d..6d62526f 100644 --- a/blender/arm/material/cycles_nodes/nodes_texture.py +++ b/blender/arm/material/cycles_nodes/nodes_texture.py @@ -375,7 +375,7 @@ def parse_sky_nishita(node: bpy.types.ShaderNodeTexSky, state: ParserState) -> v curshader.add_uniform('vec3 sunDir', link='_sunDirection') planet_radius = 6360e3 # Earth radius used in Blender - ray_origin_z = planet_radius + node.altitude * 1000 + ray_origin_z = planet_radius + node.altitude d_air = node.air_density d_dust = node.dust_density From 742b9ce1e1e6bf0e0f9920715d236cce3739d043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sun, 21 Feb 2021 17:00:29 +0100 Subject: [PATCH 047/264] Nishita sky: add support for ozone density --- Shaders/std/sky.glsl | 46 ++++++++++++++----- .../material/cycles_nodes/nodes_texture.py | 6 +-- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/Shaders/std/sky.glsl b/Shaders/std/sky.glsl index 6b85b4cc..9ad9964c 100644 --- a/Shaders/std/sky.glsl +++ b/Shaders/std/sky.glsl @@ -6,7 +6,14 @@ * Changes to the original implementation: * - r and pSun parameters of nishita_atmosphere() are already normalized * - Some original parameters of nishita_atmosphere() are replaced with pre-defined values - * - Implemented air and dust density node parameters (see Blender source) + * - Implemented air, dust and ozone density node parameters (see Blender source) + * + * Reference for the sun's limb darkening and ozone calculations: + * [Hill] Sebastien Hillaire. Physically Based Sky, Atmosphere and Cloud Rendering in Frostbite + * (https://media.contentapi.ea.com/content/dam/eacom/frostbite/files/s2016-pbs-frostbite-sky-clouds-new.pdf) + * + * Cycles code used for reference: blender/intern/sky/source/sky_nishita.cpp + * (https://github.com/blender/blender/blob/4429b4b77ef6754739a3c2b4fabd0537999e9bdc/intern/sky/source/sky_nishita.cpp) */ #ifndef _SKY_GLSL_ @@ -29,8 +36,21 @@ #define nishita_mie_dir 0.76 // Aerosols anisotropy ("direction") #define nishita_mie_dir_sq 0.5776 // Squared aerosols anisotropy +// The ozone absorption coefficients are taken from Cycles code. +// Because Cycles calculates 21 wavelengths, we use the coefficients +// which are closest to the RGB wavelengths (645nm, 510nm, 440nm). +// Precalculating values by simulating Blender's spec_to_xyz() function +// to include all 21 wavelengths gave unrealistic results +#define nishita_ozone_coeff vec3(1.59051840791988e-6, 0.00000096707041180970, 0.00000007309568762914) + +// Values from [Hill: 60] #define sun_limb_darkening_col vec3(0.397, 0.503, 0.652) +/* Approximates the density of ozone for a given sample height. Values taken from Cycles code. */ +float nishita_density_ozone(const float height) { + return (height < 10000.0 || height >= 40000.0) ? 0.0 : (height < 25000.0 ? (height - 10000.0) / 15000.0 : -((height - 40000.0) / 15000.0)); +} + /* ray-sphere intersection that assumes * the sphere is centered at the origin. * No intersection when result.x > result.y */ @@ -52,9 +72,9 @@ vec2 nishita_rsi(const vec3 r0, const vec3 rd, const float sr) { * r0: ray origin * pSun: normalized sun direction * rPlanet: planet radius - * density: (air density, dust density) + * density: (air density, dust density, ozone density) */ -vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const float rPlanet, const vec2 density) { +vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const float rPlanet, const vec3 density) { // Calculate the step size of the primary ray. vec2 p = nishita_rsi(r0, r, nishita_atmo_radius); if (p.x > p.y) return vec3(0,0,0); @@ -102,8 +122,7 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa float jTime = 0.0; // Initialize optical depth accumulators for the secondary ray. - float jOdRlh = 0.0; - float jOdMie = 0.0; + vec3 jODepth = vec3(0.0); // (Rayleigh, Mie, ozone) // Sample the secondary ray. for (int j = 0; j < nishita_jSteps; j++) { @@ -115,15 +134,22 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa float jHeight = length(jPos) - rPlanet; // Accumulate the optical depth. - jOdRlh += exp(-jHeight / nishita_rayleigh_scale) * density.x * jStepSize; - jOdMie += exp(-jHeight / nishita_mie_scale) * density.y * jStepSize; + jODepth += vec3( + exp(-jHeight / nishita_rayleigh_scale) * density.x * jStepSize, + exp(-jHeight / nishita_mie_scale) * density.y * jStepSize, + nishita_density_ozone(jHeight) * density.z * jStepSize + ); // Increment the secondary ray time. jTime += jStepSize; } // Calculate attenuation. - vec3 attn = exp(-(nishita_mie_coeff * (iOdMie + jOdMie) + nishita_rayleigh_coeff * (iOdRlh + jOdRlh))); + vec3 attn = exp(-( + nishita_mie_coeff * (iOdMie + jODepth.y) + + (nishita_rayleigh_coeff) * (iOdRlh + jODepth.x) + + nishita_ozone_coeff * jODepth.z + )); // Accumulate scattering. totalRlh += odStepRlh * attn; @@ -142,9 +168,7 @@ vec3 sun_disk(const vec3 n, const vec3 light_dir, const float disk_size, const f float dist = distance(n, light_dir) / disk_size; // Darken the edges of the sun - // Reference: https://media.contentapi.ea.com/content/dam/eacom/frostbite/files/s2016-pbs-frostbite-sky-clouds-new.pdf - // (Physically Based Sky, Atmosphere and Cloud Rendering in Frostbite by Sebastien Hillaire) - // Page 28, Page 60 (Code from [Nec96]) + // [Hill: 28, 60] (code from [Nec96]) float invDist = 1.0 - dist; float mu = sqrt(invDist * invDist); vec3 limb_darkening = 1.0 - (1.0 - pow(vec3(mu), sun_limb_darkening_col)); diff --git a/blender/arm/material/cycles_nodes/nodes_texture.py b/blender/arm/material/cycles_nodes/nodes_texture.py index 6d62526f..b3b81d1f 100644 --- a/blender/arm/material/cycles_nodes/nodes_texture.py +++ b/blender/arm/material/cycles_nodes/nodes_texture.py @@ -377,11 +377,7 @@ def parse_sky_nishita(node: bpy.types.ShaderNodeTexSky, state: ParserState) -> v planet_radius = 6360e3 # Earth radius used in Blender ray_origin_z = planet_radius + node.altitude - d_air = node.air_density - d_dust = node.dust_density - # Todo: Implement ozone density (ignored for now) - # d_ozone = node.ozone_density - density = c.to_vec2((d_air, d_dust)) + density = c.to_vec3((node.air_density, node.dust_density, node.ozone_density)) sun = '' if node.sun_disc: From 4dbf8a3b2a6768d2f691c24500b925aac0df65a9 Mon Sep 17 00:00:00 2001 From: luboslenco Date: Mon, 1 Mar 2021 09:07:33 +0100 Subject: [PATCH 048/264] Bump version --- blender/arm/props.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/props.py b/blender/arm/props.py index 47398aa3..72df10b8 100755 --- a/blender/arm/props.py +++ b/blender/arm/props.py @@ -11,7 +11,7 @@ import arm.proxy import arm.utils # Armory version -arm_version = '2021.2' +arm_version = '2021.3' arm_commit = '$Id$' def get_project_html5_copy(self): From c07de1cc25ab1816d1a0d57c72a3ec4db2d1a5aa Mon Sep 17 00:00:00 2001 From: N8n5h Date: Thu, 25 Feb 2021 15:09:23 -0300 Subject: [PATCH 049/264] Remove the inversion in shader of spot light uv for atlas With the change in iron, there is no longer a need to do this in the shader. --- Shaders/std/light.glsl | 6 +----- Shaders/std/light_mobile.glsl | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Shaders/std/light.glsl b/Shaders/std/light.glsl index aa3024ae..61c26771 100644 --- a/Shaders/std/light.glsl +++ b/Shaders/std/light.glsl @@ -185,17 +185,13 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #ifdef _Clusters vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0); #ifdef _ShadowMapAtlas - vec3 uv = lPos.xyz / lPos.w; - #ifdef _InvY - uv.y = 1.0 - uv.y; // invert Y coordinates for direct3d coordinate system - #endif direct *= shadowTest( #ifndef _SingleAtlas shadowMapAtlasSpot #else shadowMapAtlas #endif - , uv, bias + , lPos.xyz / lPos.w, bias ); #else if (index == 0) direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); diff --git a/Shaders/std/light_mobile.glsl b/Shaders/std/light_mobile.glsl index e547129c..f6a966c9 100644 --- a/Shaders/std/light_mobile.glsl +++ b/Shaders/std/light_mobile.glsl @@ -80,17 +80,13 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #ifdef _Clusters vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0); #ifdef _ShadowMapAtlas - vec3 uv = lPos.xyz / lPos.w; - #ifdef _InvY - uv.y = 1.0 - uv.y; // invert Y coordinates for direct3d coordinate system - #endif direct *= shadowTest( #ifndef _SingleAtlas shadowMapAtlasSpot #else shadowMapAtlas #endif - , uv, bias + , lPos.xyz / lPos.w, bias ); #else if (index == 0) direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); From 5f8b92f9c3bb2f0d03207a462f14b15db0104cd4 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Mon, 1 Mar 2021 01:16:17 -0300 Subject: [PATCH 050/264] Applied flip workaround for point light too for html5 and atlas The same concept for spot lights was applied for point lights; the difference is that because point lights require to do the projection in the shader, the first inversion was applied in sampleCube() when returning the uv coordinates. "_FlipY" was added to replace "_InvY" in "PCFFakeCube()" because the inversion is only necessary for shaders and not anything else, otherwise it would break rendering of other parts. --- Shaders/std/shadows.glsl | 23 ++++++++++++++--------- blender/arm/make.py | 20 ++++++++++---------- blender/arm/write_data.py | 3 +++ 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/Shaders/std/shadows.glsl b/Shaders/std/shadows.glsl index baf940a4..8aa053f3 100755 --- a/Shaders/std/shadows.glsl +++ b/Shaders/std/shadows.glsl @@ -42,7 +42,12 @@ vec2 sampleCube(vec3 dir, out int faceIndex) { uv = vec2(dir.x < 0.0 ? dir.z : -dir.z, -dir.y); } // downscale uv a little to hide seams + // transform coordinates from clip space to texture space + #ifndef _FlipY return uv * 0.9976 * ma + 0.5; + #else + return vec2(uv.x * ma, uv.y * -ma) * 0.9976 + 0.5; + #endif } #endif @@ -189,7 +194,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float vec4 pointLightTile = pointLightDataArray[lightIndex + faceIndex]; // x: tile X offset, y: tile Y offset, z: tile size relative to atlas vec2 uvtiled = pointLightTile.z * uv + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif @@ -199,7 +204,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, 0.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); @@ -207,7 +212,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, 1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif result += texture(shadowMap, vec3(uvtiled, compare)); @@ -215,7 +220,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(0.0, -1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif result += texture(shadowMap, vec3(uvtiled, compare)); @@ -223,7 +228,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, -1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif result += texture(shadowMap, vec3(uvtiled, compare)); @@ -231,7 +236,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(0.0, 1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif result += texture(shadowMap, vec3(uvtiled, compare)); @@ -239,7 +244,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, -1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif result += texture(shadowMap, vec3(uvtiled, compare)); @@ -247,7 +252,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, 0.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif result += texture(shadowMap, vec3(uvtiled, compare)); @@ -255,7 +260,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, 1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif result += texture(shadowMap, vec3(uvtiled, compare)); diff --git a/blender/arm/make.py b/blender/arm/make.py index c00fcc52..4538a9e6 100755 --- a/blender/arm/make.py +++ b/blender/arm/make.py @@ -211,7 +211,7 @@ def export_data(fp, sdk_path): resx, resy = arm.utils.get_render_resolution(arm.utils.get_active_scene()) if wrd.arm_write_config: write_data.write_config(resx, resy) - + # Change project version (Build, Publish) if (not state.is_play) and (wrd.arm_project_version_autoinc): wrd.arm_project_version = arm.utils.arm.utils.change_version_project(wrd.arm_project_version) @@ -654,12 +654,12 @@ def build_success(): state.proc_publish_build = run_proc(cmd, done_gradlew_build) else: print('\nBuilding APK Warning: ANDROID_SDK_ROOT is not specified in environment variables and "Android SDK Path" setting is not specified in preferences: \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" in the preferences, then repeat operation "Publish"') - + # HTML5 After Publish if target_name.startswith('html5'): if len(arm.utils.get_html5_copy_path()) > 0 and (wrd.arm_project_html5_copy): project_name = arm.utils.safesrc(wrd.arm_project_name +'-'+ wrd.arm_project_version) - dst = os.path.join(arm.utils.get_html5_copy_path(), project_name) + dst = os.path.join(arm.utils.get_html5_copy_path(), project_name) if os.path.exists(dst): shutil.rmtree(dst) try: @@ -673,10 +673,10 @@ def build_success(): link_html5_app = arm.utils.get_link_web_server() +'/'+ project_name print("Running a browser with a link " + link_html5_app) webbrowser.open(link_html5_app) - + # Windows After Publish if target_name.startswith('windows'): - list_vs = [] + list_vs = [] err = '' # Print message project_name = arm.utils.safesrc(wrd.arm_project_name +'-'+ wrd.arm_project_version) @@ -707,18 +707,18 @@ def build_success(): for vs in list_vs: print('- ' + vs[1] + ' (version ' + vs[3] +')') return - # Current VS + # Current VS vs_path = '' for vs in list_vs: if vs[0] == wrd.arm_project_win_list_vs: vs_path = vs[2] break - # Open in Visual Studio + # Open in Visual Studio if int(wrd.arm_project_win_build) == 1: cmd = os.path.join('start "' + vs_path, 'Common7', 'IDE', 'devenv.exe" "' + os.path.join(project_path, project_name + '.sln"')) subprocess.Popen(cmd, shell=True) # Compile - if int(wrd.arm_project_win_build) > 1: + if int(wrd.arm_project_win_build) > 1: bits = '64' if wrd.arm_project_win_build_arch == 'x64' else '32' # vcvars cmd = os.path.join(vs_path, 'VC', 'Auxiliary', 'Build', 'vcvars' + bits + '.bat') @@ -792,7 +792,7 @@ def done_vs_vars(): # MSBuild wrd = bpy.data.worlds['Arm'] list_vs, err = arm.utils.get_list_installed_vs(True, True, True) - # Current VS + # Current VS vs_path = '' vs_name = '' for vs in list_vs: @@ -851,7 +851,7 @@ def done_vs_build(): os.chdir(res_path) # set work folder subprocess.Popen(cmd, shell=True) # Open Build Directory - if wrd.arm_project_win_build_open: + if wrd.arm_project_win_build_open: arm.utils.open_folder(path) state.redraw_ui = True else: diff --git a/blender/arm/write_data.py b/blender/arm/write_data.py index 9f88e335..439e2984 100755 --- a/blender/arm/write_data.py +++ b/blender/arm/write_data.py @@ -537,6 +537,9 @@ def write_compiledglsl(defs, make_variants): #endif """) + if state.target == 'html5': + f.write("#define _FlipY\n") + f.write("""const float PI = 3.1415926535; const float PI2 = PI * 2.0; const vec2 shadowmapSize = vec2(""" + str(shadowmap_size) + """, """ + str(shadowmap_size) + """); From cf99e5a38288bf5672abe1c830ad3767b06bae89 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Mon, 1 Mar 2021 18:37:54 -0300 Subject: [PATCH 051/264] Fixed point lights breaking for Krom Windows with shadow map atlas https://github.com/armory3d/armory/issues/2110#issuecomment-787913813 --- Shaders/std/shadows.glsl | 8 ++++++-- blender/arm/write_data.py | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Shaders/std/shadows.glsl b/Shaders/std/shadows.glsl index 8aa053f3..cafde49e 100755 --- a/Shaders/std/shadows.glsl +++ b/Shaders/std/shadows.glsl @@ -44,9 +44,13 @@ vec2 sampleCube(vec3 dir, out int faceIndex) { // downscale uv a little to hide seams // transform coordinates from clip space to texture space #ifndef _FlipY - return uv * 0.9976 * ma + 0.5; + return uv * 0.9976 * ma + 0.5; #else - return vec2(uv.x * ma, uv.y * -ma) * 0.9976 + 0.5; + #ifdef HLSL + return uv * 0.9976 * ma + 0.5; + #else + return vec2(uv.x * ma, uv.y * -ma) * 0.9976 + 0.5; + #endif #endif } #endif diff --git a/blender/arm/write_data.py b/blender/arm/write_data.py index 439e2984..c2d1c54d 100755 --- a/blender/arm/write_data.py +++ b/blender/arm/write_data.py @@ -537,7 +537,7 @@ def write_compiledglsl(defs, make_variants): #endif """) - if state.target == 'html5': + if state.target == 'html5' or arm.utils.get_gapi() == 'direct3d11': f.write("#define _FlipY\n") f.write("""const float PI = 3.1415926535; From 72dde10fb0ed3837bbfe0c090e3f66813a9547ac Mon Sep 17 00:00:00 2001 From: N8n5h Date: Tue, 2 Mar 2021 19:57:40 -0300 Subject: [PATCH 052/264] Fix forward+single atlas failing to compile with just 1 light This should fix an issue where the uniform for shadowMapAtlas used with the single atlas option was not added when having just 1 light. --- blender/arm/material/make_cluster.py | 13 +++++++++---- blender/arm/material/make_mesh.py | 4 ++-- blender/arm/material/shader.py | 3 +++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/blender/arm/material/make_cluster.py b/blender/arm/material/make_cluster.py index 34589d42..1ac91d5f 100644 --- a/blender/arm/material/make_cluster.py +++ b/blender/arm/material/make_cluster.py @@ -4,7 +4,7 @@ def write(vert, frag): wrd = bpy.data.worlds['Arm'] is_shadows = '_ShadowMap' in wrd.world_defs is_shadows_atlas = '_ShadowMapAtlas' in wrd.world_defs - is_single_atlas = is_shadows_atlas and '_SingleAtlas' in wrd.world_defs + is_single_atlas = '_SingleAtlas' in wrd.world_defs frag.add_include_front('std/clusters.glsl') frag.add_uniform('vec2 cameraProj', link='_cameraPlaneProj') @@ -17,6 +17,8 @@ def write(vert, frag): if is_shadows_atlas: if not is_single_atlas: frag.add_uniform('sampler2DShadow shadowMapAtlasPoint', included=True) + else: + frag.add_uniform('sampler2DShadow shadowMapAtlas', top=True) frag.add_uniform('vec4 pointLightDataArray[maxLightsCluster]', link='_pointLightsAtlasArray', included=True) else: frag.add_uniform('samplerCubeShadow shadowMapPoint[4]', included=True) @@ -36,9 +38,12 @@ def write(vert, frag): frag.write('int numSpots = int(texelFetch(clustersData, ivec2(clusterI, 1 + maxLightsCluster), 0).r * 255);') frag.write('int numPoints = numLights - numSpots;') if is_shadows: - if is_shadows_atlas and not is_single_atlas: - frag.add_uniform(f'sampler2DShadow shadowMapAtlasSpot', included=True) - elif not is_shadows_atlas: + if is_shadows_atlas: + if not is_single_atlas: + frag.add_uniform('sampler2DShadow shadowMapAtlasSpot', included=True) + else: + frag.add_uniform('sampler2DShadow shadowMapAtlas', top=True) + else: frag.add_uniform('sampler2DShadow shadowMapSpot[4]', included=True) # FIXME: type is actually mat4, but otherwise it will not be set as floats when writing the shaders' json files frag.add_uniform('vec4 LWVPSpotArray[maxLightsCluster]', link='_biasLightWorldViewProjectionMatrixSpotArray', included=True) diff --git a/blender/arm/material/make_mesh.py b/blender/arm/material/make_mesh.py index c64ec408..9ec18bbe 100644 --- a/blender/arm/material/make_mesh.py +++ b/blender/arm/material/make_mesh.py @@ -358,9 +358,9 @@ def make_forward_mobile(con_mesh): is_shadows = '_ShadowMap' in wrd.world_defs is_shadows_atlas = '_ShadowMapAtlas' in wrd.world_defs - is_single_atlas = is_shadows_atlas and '_SingleAtlas' in wrd.world_defs shadowmap_sun = 'shadowMap' if is_shadows_atlas: + is_single_atlas = '_SingleAtlas' in wrd.world_defs shadowmap_sun = 'shadowMapAtlasSun' if not is_single_atlas else 'shadowMapAtlas' frag.add_uniform('vec2 smSizeUniform', '_shadowMapSize', included=True) frag.write('vec3 direct = vec3(0.0);') @@ -575,7 +575,7 @@ def make_forward_base(con_mesh, parse_opacity=False, transluc_pass=False): arm_discard = mat_state.material.arm_discard make_base(con_mesh, parse_opacity=(parse_opacity or arm_discard)) - + blend = mat_state.material.arm_blending vert = con_mesh.vert diff --git a/blender/arm/material/shader.py b/blender/arm/material/shader.py index 48539e96..999ea293 100644 --- a/blender/arm/material/shader.py +++ b/blender/arm/material/shader.py @@ -223,6 +223,9 @@ class Shader: self.outs.append(s) def add_uniform(self, s, link=None, included=False, top=False): + # prevent duplicates + if s in self.uniforms or s in self.uniforms_top: + return ar = s.split(' ') # layout(RGBA8) image3D voxels utype = ar[-2] From 4009d9d3fe0e7191fd6cfc20774b61fc20200417 Mon Sep 17 00:00:00 2001 From: Alexander Kleemann Date: Wed, 10 Mar 2021 20:48:46 +0100 Subject: [PATCH 053/264] Fix for an error with NoneType materials Solves an error where an unallocated material slot caused a bug --- blender/arm/make_renderpath.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/blender/arm/make_renderpath.py b/blender/arm/make_renderpath.py index 051ec4dc..ef296d21 100755 --- a/blender/arm/make_renderpath.py +++ b/blender/arm/make_renderpath.py @@ -373,8 +373,11 @@ def build(): if obj.type == "MESH": for slot in obj.material_slots: mat = slot.material - if mat.arm_ignore_irradiance: - ignoreIrr = True + + if mat: #Check if not NoneType + + if mat.arm_ignore_irradiance: + ignoreIrr = True if ignoreIrr: wrd.world_defs += '_IgnoreIrr' From e5d6c47e3e10ffd66a9ae5024fb444cc0de5da6c Mon Sep 17 00:00:00 2001 From: Alexander Kleemann Date: Wed, 10 Mar 2021 20:49:51 +0100 Subject: [PATCH 054/264] Fix Blender exit error (Keymap winman hangs) --- blender/arm/keymap.py | 2 ++ blender/arm/lightmapper/keymap/__init__.py | 7 ------- blender/arm/props_bake.py | 6 ++---- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/blender/arm/keymap.py b/blender/arm/keymap.py index b4a019ea..77e09183 100644 --- a/blender/arm/keymap.py +++ b/blender/arm/keymap.py @@ -17,6 +17,8 @@ def register(): km = addon_keyconfig.keymaps.new(name='Window', space_type='EMPTY', region_type="WINDOW") km.keymap_items.new(props_ui.ArmoryPlayButton.bl_idname, type='F5', value='PRESS') + km.keymap_items.new("tlm.build_lightmaps", type='F6', value='PRESS') + km.keymap_items.new("tlm.clean_lightmaps", type='F7', value='PRESS') arm_keymaps.append(km) def unregister(): diff --git a/blender/arm/lightmapper/keymap/__init__.py b/blender/arm/lightmapper/keymap/__init__.py index 079b3c5d..e69de29b 100644 --- a/blender/arm/lightmapper/keymap/__init__.py +++ b/blender/arm/lightmapper/keymap/__init__.py @@ -1,7 +0,0 @@ -from . import keymap - -def register(): - keymap.register() - -def unregister(): - keymap.unregister() \ No newline at end of file diff --git a/blender/arm/props_bake.py b/blender/arm/props_bake.py index 25bff93a..e433e682 100644 --- a/blender/arm/props_bake.py +++ b/blender/arm/props_bake.py @@ -3,7 +3,7 @@ import arm.assets import bpy from bpy.types import Menu, Panel, UIList from bpy.props import * -from arm.lightmapper import operators, properties, utility, keymap +from arm.lightmapper import operators, properties, utility class ArmBakeListItem(bpy.types.PropertyGroup): obj: PointerProperty(type=bpy.types.Object, description="The object to bake") @@ -361,7 +361,6 @@ def register(): operators.register() properties.register() - keymap.register() def unregister(): bpy.utils.unregister_class(ArmBakeListItem) @@ -380,5 +379,4 @@ def unregister(): #Unregister lightmapper operators.unregister() - properties.unregister() - keymap.unregister() \ No newline at end of file + properties.unregister() \ No newline at end of file From 5f55b00710fd780b2adce9373e1609acd30aa028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 11 Mar 2021 23:16:44 +0100 Subject: [PATCH 055/264] Begin with Nishita LUT implementation for better performance --- Shaders/std/sky.glsl | 17 +++--- Sources/armory/math/Helper.hx | 9 ++++ Sources/armory/object/Uniforms.hx | 6 ++- Sources/armory/renderpath/Nishita.hx | 80 ++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 Sources/armory/renderpath/Nishita.hx diff --git a/Shaders/std/sky.glsl b/Shaders/std/sky.glsl index 9ad9964c..61f93c84 100644 --- a/Shaders/std/sky.glsl +++ b/Shaders/std/sky.glsl @@ -19,6 +19,9 @@ #ifndef _SKY_GLSL_ #define _SKY_GLSL_ +// OpenGl ES doesn't support 1D textures so we use a 1 px height sampler2D here... +uniform sampler2D nishitaLUT; + #define PI 3.141592 #define nishita_iSteps 16 @@ -46,6 +49,8 @@ // Values from [Hill: 60] #define sun_limb_darkening_col vec3(0.397, 0.503, 0.652) +#define heightToLUT(h) (textureLod(nishitaLUT, vec2(clamp(h * (1 / 60000.0), 0.0, 1.0), 0.0), 0.0).xyz * 10.0) + /* Approximates the density of ozone for a given sample height. Values taken from Cycles code. */ float nishita_density_ozone(const float height) { return (height < 10000.0 || height >= 40000.0) ? 0.0 : (height < 25000.0 ? (height - 10000.0) / 15000.0 : -((height - 40000.0) / 15000.0)); @@ -108,8 +113,9 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa float iHeight = length(iPos) - rPlanet; // Calculate the optical depth of the Rayleigh and Mie scattering for this step. - float odStepRlh = exp(-iHeight / nishita_rayleigh_scale) * density.x * iStepSize; - float odStepMie = exp(-iHeight / nishita_mie_scale) * density.y * iStepSize; + vec3 iLookup = heightToLUT(iHeight); + float odStepRlh = iLookup.x * iStepSize; + float odStepMie = iLookup.y * iStepSize; // Accumulate optical depth. iOdRlh += odStepRlh; @@ -134,11 +140,8 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa float jHeight = length(jPos) - rPlanet; // Accumulate the optical depth. - jODepth += vec3( - exp(-jHeight / nishita_rayleigh_scale) * density.x * jStepSize, - exp(-jHeight / nishita_mie_scale) * density.y * jStepSize, - nishita_density_ozone(jHeight) * density.z * jStepSize - ); + vec3 jLookup = heightToLUT(jHeight); + jODepth += jLookup * jStepSize; // Increment the secondary ray time. jTime += jStepSize; diff --git a/Sources/armory/math/Helper.hx b/Sources/armory/math/Helper.hx index 03351492..d3579d1e 100644 --- a/Sources/armory/math/Helper.hx +++ b/Sources/armory/math/Helper.hx @@ -72,4 +72,13 @@ class Helper { if (value <= leftMin) return rightMin; return map(value, leftMin, leftMax, rightMin, rightMax); } + + /** + Return the sign of the given value represented as `1.0` (positive value) + or `-1.0` (negative value). The sign of `0` is `0`. + **/ + public static inline function sign(value: Float): Float { + if (value == 0) return 0; + return (value < 0) ? -1.0 : 1.0; + } } diff --git a/Sources/armory/object/Uniforms.hx b/Sources/armory/object/Uniforms.hx index 91c55f30..64d20af2 100644 --- a/Sources/armory/object/Uniforms.hx +++ b/Sources/armory/object/Uniforms.hx @@ -19,8 +19,12 @@ class Uniforms { } public static function textureLink(object: Object, mat: MaterialData, link: String): kha.Image { + if (link == "_nishitaLUT") { + if (armory.renderpath.Nishita.data == null) armory.renderpath.Nishita.recompute(Scene.active.world); + return armory.renderpath.Nishita.data.optDepthLUT; + } #if arm_ltc - if (link == "_ltcMat") { + else if (link == "_ltcMat") { if (armory.data.ConstData.ltcMatTex == null) armory.data.ConstData.initLTC(); return armory.data.ConstData.ltcMatTex; } diff --git a/Sources/armory/renderpath/Nishita.hx b/Sources/armory/renderpath/Nishita.hx new file mode 100644 index 00000000..c968798d --- /dev/null +++ b/Sources/armory/renderpath/Nishita.hx @@ -0,0 +1,80 @@ +package armory.renderpath; + +import kha.FastFloat; +import kha.graphics4.TextureFormat; +import kha.graphics4.Usage; + +import iron.data.WorldData; + +import armory.math.Helper; + +class Nishita { + + public static var data: NishitaData = null; + + public static function recompute(world: WorldData) { + if (world == null || world.raw.sun_direction == null) return; + if (data == null) data = new NishitaData(); + + // TODO + data.recompute(1.0, 1.0, 1.0); + } +} + +class NishitaData { + static inline var LUT_WIDTH = 16; + + /** Maximum ray height as defined by Cycles **/ + static inline var MAX_HEIGHT = 60000; + + static inline var RAYLEIGH_SCALE = 8e3; + static inline var MIE_SCALE = 1.2e3; + + public var optDepthLUT: kha.Image; + + public function new() {} + + function getOzoneDensity(height: FastFloat): FastFloat { + if (height < 10000.0 || height >= 40000.0) { + return 0.0; + } + if (height < 25000.0) { + return (height - 10000.0) / 15000.0; + } + return -((height - 40000.0) / 15000.0); + } + + /** + The RGBA texture layout looks as follows: + R = Rayleigh optical depth at height \in [0, 60000] + G = Mie optical depth at height \in [0, 60000] + B = Ozone optical depth at height \in [0, 60000] + A = Unused + **/ + public function recompute(densityFacAir: FastFloat, densityFacDust: FastFloat, densityFacOzone: FastFloat) { + optDepthLUT = kha.Image.create(LUT_WIDTH, 1, TextureFormat.RGBA32, Usage.StaticUsage); + + var textureData = optDepthLUT.lock(); + for (i in 0...LUT_WIDTH) { + // Get the height for each LUT pixel i (in [-1, 1] range) + var height = (i / LUT_WIDTH) * 2 - 1; + + // Use quadratic height for better horizon precision + // See https://sebh.github.io/publications/egsr2020.pdf (5.3) + height = 0.5 + 0.5 * Helper.sign(height) * Math.sqrt(Math.abs(height)); + height *= MAX_HEIGHT; // Denormalize + + // Make sure we use 32 bit floats + var optDepthRayleigh: FastFloat = Math.exp(-height / RAYLEIGH_SCALE) * densityFacAir; + var optDepthMie: FastFloat = Math.exp(-height / MIE_SCALE) * densityFacDust; + var optDepthOzone: FastFloat = getOzoneDensity(height) * densityFacOzone; + + // 10 is the maximum density, so we divide by it to be able to use normalized values + textureData.set(i * 4 + 0, Std.int(optDepthRayleigh * 255 / 10)); + textureData.set(i * 4 + 1, Std.int(optDepthMie * 255 / 10)); + textureData.set(i * 4 + 2, Std.int(optDepthOzone * 255 / 10)); + textureData.set(i * 4 + 3, 255); // Unused + } + optDepthLUT.unlock(); + } +} From 8e972f75fd25933f5a6ece83e1137573b2023383 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Sat, 13 Mar 2021 13:24:53 -0300 Subject: [PATCH 056/264] Fix light visual artifact when close to the camera This fixes a current problem with lights that occurs when getting too close to the camera. The issue is related to the clustering algorithm and cluster near. Cluster near explanation: Because of the logarithmic nature of how the Z slice is calculated, clusters slices get "burned" too quickly at the start of the frustum, which is not ideal. To aid this, and offset is applied to the start of the cluster frustum, and that is clusterNear. Cluster near solves that problem but creates another one: There is a gap between the near plane and the cluster near offset that gets completely ignored when calculating. And this results in the visual artifact of slices not being affected by light whatsoever. The proposed solution is make the gap resolve to the first slice of Z clusters. --- Shaders/std/clusters.glsl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Shaders/std/clusters.glsl b/Shaders/std/clusters.glsl index 04570ef6..9a8b66e6 100644 --- a/Shaders/std/clusters.glsl +++ b/Shaders/std/clusters.glsl @@ -8,6 +8,10 @@ int getClusterI(vec2 tc, float viewz, vec2 cameraPlane) { float z = log(viewz - cnear + 1.0) / log(cameraPlane.y - cnear + 1.0); sliceZ = int(z * (clusterSlices.z - 1)) + 1; } + // address gap between near plane and cluster near offset + else if (viewz >= cameraPlane.x) { + sliceZ = 1; + } return int(tc.x * clusterSlices.x) + int(int(tc.y * clusterSlices.y) * clusterSlices.x) + int(sliceZ * clusterSlices.x * clusterSlices.y); From 404a1248a1f371c3f29b1a4f6846ff7c3346338b Mon Sep 17 00:00:00 2001 From: N8n5h Date: Sat, 13 Mar 2021 13:25:34 -0300 Subject: [PATCH 057/264] Make clusterNear be the same as in LightObject --- blender/arm/write_data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blender/arm/write_data.py b/blender/arm/write_data.py index c2d1c54d..4d2aadda 100755 --- a/blender/arm/write_data.py +++ b/blender/arm/write_data.py @@ -253,7 +253,7 @@ project.addSources('Sources'); if wrd.arm_debug_console: assets.add_khafile_def('arm_debug') khafile.write(add_shaders(sdk_path + "/armory/Shaders/debug_draw/**", rel_path=do_relpath_sdk)) - + if not is_publish and state.target == 'html5': khafile.write("project.addParameter('--debug');\n") @@ -711,7 +711,7 @@ const float voxelgiAperture = """ + str(round(rpdat.arm_voxelgi_aperture * 100) f.write( """const int maxLights = """ + max_lights + """; const int maxLightsCluster = """ + max_lights_clusters + """; -const float clusterNear = 4.0; +const float clusterNear = 3.0; """) f.write(add_compiledglsl + '\n') # External defined constants From 7d9c6ce7c7bfe17b4b610ccd279dd99943269aa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 13 Mar 2021 21:12:12 +0100 Subject: [PATCH 058/264] Make @prop recognition much more robust --- blender/arm/props_traits.py | 3 +- blender/arm/props_traits_props.py | 1 + blender/arm/utils.py | 178 ++++++++++++++---------------- 3 files changed, 83 insertions(+), 99 deletions(-) diff --git a/blender/arm/props_traits.py b/blender/arm/props_traits.py index a11beade..7f69612e 100755 --- a/blender/arm/props_traits.py +++ b/blender/arm/props_traits.py @@ -882,9 +882,10 @@ def draw_traits(layout, obj, is_object): 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: - box.label(text=warning.warning) + col.label(text=f'"{warning.propName}": {warning.warning}') propsrow = layout.row() propsrows = max(len(item.arm_traitpropslist), 6) diff --git a/blender/arm/props_traits_props.py b/blender/arm/props_traits_props.py index ea9dc23d..24931612 100644 --- a/blender/arm/props_traits_props.py +++ b/blender/arm/props_traits_props.py @@ -32,6 +32,7 @@ def filter_objects(item, b_object): class ArmTraitPropWarning(bpy.types.PropertyGroup): + propName: StringProperty(name="Property Name") warning: StringProperty(name="Warning") diff --git a/blender/arm/utils.py b/blender/arm/utils.py index 82ff4f9f..305f79da 100755 --- a/blender/arm/utils.py +++ b/blender/arm/utils.py @@ -4,7 +4,7 @@ import os import platform import re import subprocess -from typing import Any +from typing import Any, Dict, List, Optional, Tuple import webbrowser import shlex import locale @@ -292,116 +292,97 @@ def fetch_bundled_script_names(): for file in glob.glob('*.hx'): wrd.arm_bundled_scripts_list.add().name = file.rsplit('.', 1)[0] + script_props = {} script_props_defaults = {} -script_warnings = {} -def fetch_script_props(file): - with open(file, encoding="utf-8") as f: - name = file.rsplit('.', 1)[0] - if 'Sources' in name: - name = name[name.index('Sources') + 8:] - if '/' in name: - name = name.replace('/', '.') - if '\\' in file: - name = name.replace('\\', '.') +script_warnings: Dict[str, List[Tuple[str, str]]] = {} # Script name -> List of (identifier, warning message) - script_props[name] = [] - script_props_defaults[name] = [] - script_warnings[name] = [] +# See https://regex101.com/r/bbrCzN/7 +RX_MODIFIERS = r'(?P(?:public\s+|private\s+|static\s+|inline\s+|final\s+)*)?' # Optional modifiers +RX_IDENTIFIER = r'(?P[_$a-z]+[_a-z0-9]*)' # Variable name, follow Haxe rules +RX_TYPE = r'(?::\s+(?P[_a-z]+[\._a-z0-9]*))?' # Optional type annotation +RX_VALUE = r'(?:\s*=\s*(?P(?:\".*\")|(?:[^;]+)|))?' # Optional default value - lines = f.read().splitlines() +PROP_REGEX_RAW = fr'@prop\s+{RX_MODIFIERS}var\s+{RX_IDENTIFIER}{RX_TYPE}{RX_VALUE};' +PROP_REGEX = re.compile(PROP_REGEX_RAW, re.IGNORECASE) +def fetch_script_props(filename: str): + """Parses @prop declarations from the given Haxe script.""" + with open(filename, 'r', encoding='utf-8') as sourcefile: + source = sourcefile.read() - # Read next line - read_prop = False - for lineno, line in enumerate(lines): - # enumerate() starts with 0 - lineno += 1 + if source == '': + return - if not read_prop: - read_prop = line.lstrip().startswith('@prop') + name = filename.rsplit('.', 1)[0] + + # Convert the name into a package path relative to the "Sources" dir + if 'Sources' in name: + name = name[name.index('Sources') + 8:] + if '/' in name: + name = name.replace('/', '.') + if '\\' in filename: + name = name.replace('\\', '.') + + script_props[name] = [] + script_props_defaults[name] = [] + script_warnings[name] = [] + + for match in re.finditer(PROP_REGEX, source): + + p_modifiers: Optional[str] = match.group('modifiers') + p_identifier: str = match.group('identifier') + p_type: Optional[str] = match.group('type') + p_default_val: Optional[str] = match.group('value') + + if p_modifiers is not None: + if 'static' in p_modifiers: + script_warnings[name].append((p_identifier, 'static properties may result in unwanted behaviour!')) + if 'inline' in p_modifiers: + script_warnings[name].append((p_identifier, 'inline properties are not supported!')) + continue + if 'final' in p_modifiers: + script_warnings[name].append((p_identifier, 'final properties are not supported!')) continue - if read_prop: - if 'var ' in line: - # Line of code - code_ref = line.split('var ')[1].split(';')[0] - else: - script_warnings[name].append(f"Line {lineno - 1}: Unused @prop") - read_prop = line.lstrip().startswith('@prop') - continue + # Property type is annotated + if p_type is not None: + if p_type.startswith("iron.object."): + p_type = p_type[12:] + elif p_type.startswith("iron.math."): + p_type = p_type[10:] - valid_prop = False + type_default_val = get_type_default_value(p_type) + if type_default_val is None: + script_warnings[name].append((p_identifier, f'unsupported type "{p_type}"!')) + continue - # Declaration = Assignment; - var_sides = code_ref.split('=') - # DeclarationName: DeclarationType - decl_sides = var_sides[0].split(':') + # Default value exists + if p_default_val is not None: + # Remove string quotes + p_default_val = p_default_val.replace('\'', '').replace('"', '') + else: + p_default_val = type_default_val - prop_name = decl_sides[0].strip() + # Default value is given instead, try to infer the properties type from it + elif p_default_val is not None: + p_type = get_prop_type_from_value(p_default_val) - if 'static ' in line: - # Static properties can be overwritten multiple times - # from multiple property lists - script_warnings[name].append(f"Line {lineno} (\"{prop_name}\"): Static properties may result in undefined behaviours!") + # Type is not recognized + if p_type is None: + script_warnings[name].append((p_identifier, 'could not infer property type from given value!')) + continue + if p_type == "String": + p_default_val = p_default_val.replace('\'', '').replace('"', '') - # If the prop type is annotated in the code - # (= declaration has two parts) - if len(decl_sides) > 1: - prop_type = decl_sides[1].strip() - if prop_type.startswith("iron.object."): - prop_type = prop_type[12:] - elif prop_type.startswith("iron.math."): - prop_type = prop_type[10:] + else: + script_warnings[name].append((p_identifier, 'missing type or default value!')) + continue - # Default value exists - if len(var_sides) > 1 and var_sides[1].strip() != "": - # Type is not supported - if get_type_default_value(prop_type) is None: - script_warnings[name].append(f"Line {lineno} (\"{prop_name}\"): Type {prop_type} is not supported!") - read_prop = False - continue + # Register prop + prop = (p_identifier, p_type) + script_props[name].append(prop) + script_props_defaults[name].append(p_default_val) - prop_value = var_sides[1].replace('\'', '').replace('"', '').strip() - - else: - prop_value = get_type_default_value(prop_type) - - # Type is not supported - if prop_value is None: - script_warnings[name].append(f"Line {lineno} (\"{prop_name}\"): Type {prop_type} is not supported!") - read_prop = False - continue - - valid_prop = True - - # Default value exists - elif len(var_sides) > 1 and var_sides[1].strip() != "": - prop_value = var_sides[1].strip() - prop_type = get_prop_type_from_value(prop_value) - - # Type is not recognized - if prop_type is None: - script_warnings[name].append(f"Line {lineno} (\"{prop_name}\"): Property type not recognized!") - read_prop = False - continue - if prop_type == "String": - prop_value = prop_value.replace('\'', '').replace('"', '') - - valid_prop = True - - else: - script_warnings[name].append(f"Line {lineno} (\"{prop_name}\"): Not a valid property!") - read_prop = False - continue - - prop = (prop_name, prop_type) - - # Register prop - if valid_prop: - script_props[name].append(prop) - script_props_defaults[name].append(prop_value) - - read_prop = False def get_prop_type_from_value(value: str): """ @@ -555,7 +536,8 @@ def fetch_prop(o): item.arm_traitpropswarnings.clear() for warning in warnings: entry = item.arm_traitpropswarnings.add() - entry.warning = warning + entry.propName = warning[0] + entry.warning = warning[1] def fetch_bundled_trait_props(): # Bundled script props @@ -1096,4 +1078,4 @@ def register(local_sdk=False): use_local_sdk = local_sdk def unregister(): - pass \ No newline at end of file + pass From 4ad68304e52f9dd21627cc4a44fd918fbba93d1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 13 Mar 2021 21:22:42 +0100 Subject: [PATCH 059/264] Improve `@prop` warning messages --- blender/arm/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/blender/arm/utils.py b/blender/arm/utils.py index 305f79da..fe8128f0 100755 --- a/blender/arm/utils.py +++ b/blender/arm/utils.py @@ -336,12 +336,12 @@ def fetch_script_props(filename: str): if p_modifiers is not None: if 'static' in p_modifiers: - script_warnings[name].append((p_identifier, 'static properties may result in unwanted behaviour!')) + script_warnings[name].append((p_identifier, '`static` modifier might cause unwanted behaviour!')) if 'inline' in p_modifiers: - script_warnings[name].append((p_identifier, 'inline properties are not supported!')) + script_warnings[name].append((p_identifier, '`inline` modifier is not supported!')) continue if 'final' in p_modifiers: - script_warnings[name].append((p_identifier, 'final properties are not supported!')) + script_warnings[name].append((p_identifier, '`final` modifier is not supported!')) continue # Property type is annotated @@ -353,7 +353,7 @@ def fetch_script_props(filename: str): type_default_val = get_type_default_value(p_type) if type_default_val is None: - script_warnings[name].append((p_identifier, f'unsupported type "{p_type}"!')) + script_warnings[name].append((p_identifier, f'unsupported type `{p_type}`!')) continue # Default value exists From b7fef30b700a9fddaecf903ffd5286900ef8eecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sun, 14 Mar 2021 15:50:27 +0100 Subject: [PATCH 060/264] Further improve handling of final trait props --- blender/arm/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/blender/arm/utils.py b/blender/arm/utils.py index fe8128f0..0c0cdc43 100755 --- a/blender/arm/utils.py +++ b/blender/arm/utils.py @@ -297,13 +297,13 @@ script_props = {} script_props_defaults = {} script_warnings: Dict[str, List[Tuple[str, str]]] = {} # Script name -> List of (identifier, warning message) -# See https://regex101.com/r/bbrCzN/7 +# See https://regex101.com/r/bbrCzN/8 RX_MODIFIERS = r'(?P(?:public\s+|private\s+|static\s+|inline\s+|final\s+)*)?' # Optional modifiers RX_IDENTIFIER = r'(?P[_$a-z]+[_a-z0-9]*)' # Variable name, follow Haxe rules RX_TYPE = r'(?::\s+(?P[_a-z]+[\._a-z0-9]*))?' # Optional type annotation RX_VALUE = r'(?:\s*=\s*(?P(?:\".*\")|(?:[^;]+)|))?' # Optional default value -PROP_REGEX_RAW = fr'@prop\s+{RX_MODIFIERS}var\s+{RX_IDENTIFIER}{RX_TYPE}{RX_VALUE};' +PROP_REGEX_RAW = fr'@prop\s+{RX_MODIFIERS}(?Pvar|final)\s+{RX_IDENTIFIER}{RX_TYPE}{RX_VALUE};' PROP_REGEX = re.compile(PROP_REGEX_RAW, re.IGNORECASE) def fetch_script_props(filename: str): """Parses @prop declarations from the given Haxe script.""" @@ -340,8 +340,8 @@ def fetch_script_props(filename: str): if 'inline' in p_modifiers: script_warnings[name].append((p_identifier, '`inline` modifier is not supported!')) continue - if 'final' in p_modifiers: - script_warnings[name].append((p_identifier, '`final` modifier is not supported!')) + if 'final' in p_modifiers or match.group('attr_type') == 'final': + script_warnings[name].append((p_identifier, '`final` properties are not supported!')) continue # Property type is annotated From efcef7dd2514f1d5f95e0d20c520409456c7e3ad Mon Sep 17 00:00:00 2001 From: N8n5h Date: Sun, 14 Mar 2021 21:51:36 -0300 Subject: [PATCH 061/264] Fix shadow rendering for deferred and forward mobile --- .../deferred_light.frag.glsl | 3 + .../deferred_light_mobile.json | 5 ++ Shaders/std/light_mobile.glsl | 55 ++++++++++--------- 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/Shaders/deferred_light_mobile/deferred_light.frag.glsl b/Shaders/deferred_light_mobile/deferred_light.frag.glsl index cc8ce302..f1b32985 100644 --- a/Shaders/deferred_light_mobile/deferred_light.frag.glsl +++ b/Shaders/deferred_light_mobile/deferred_light.frag.glsl @@ -29,6 +29,9 @@ uniform int envmapNumMipmaps; uniform vec3 backgroundCol; #endif +#ifdef _SMSizeUniform +//!uniform vec2 smSizeUniform; +#endif uniform vec2 cameraProj; uniform vec3 eye; uniform vec3 eyeLook; diff --git a/Shaders/deferred_light_mobile/deferred_light_mobile.json b/Shaders/deferred_light_mobile/deferred_light_mobile.json index 620e27e1..48774740 100644 --- a/Shaders/deferred_light_mobile/deferred_light_mobile.json +++ b/Shaders/deferred_light_mobile/deferred_light_mobile.json @@ -107,6 +107,11 @@ "link": "_viewProjectionMatrix", "ifdef": ["_SSRS"] }, + { + "name": "smSizeUniform", + "link": "_shadowMapSize", + "ifdef": ["_SMSizeUniform"] + }, { "name": "lightProj", "link": "_lightPlaneProj", diff --git a/Shaders/std/light_mobile.glsl b/Shaders/std/light_mobile.glsl index f6a966c9..9686f844 100644 --- a/Shaders/std/light_mobile.glsl +++ b/Shaders/std/light_mobile.glsl @@ -8,39 +8,39 @@ #endif #ifdef _ShadowMap -#ifdef _SinglePoint - #ifdef _Spot - uniform sampler2DShadow shadowMapSpot[1]; - uniform mat4 LWVPSpot[1]; - #else - uniform samplerCubeShadow shadowMapPoint[1]; - uniform vec2 lightProj; + #ifdef _SinglePoint + #ifdef _Spot + uniform sampler2DShadow shadowMapSpot[1]; + uniform mat4 LWVPSpot[1]; + #else + uniform samplerCubeShadow shadowMapPoint[1]; + uniform vec2 lightProj; + #endif #endif -#endif -#ifdef _Clusters - #ifdef _SingleAtlas - //!uniform sampler2DShadow shadowMapAtlas; - #endif - uniform vec2 lightProj; - #ifdef _ShadowMapAtlas - #ifndef _SingleAtlas - uniform sampler2DShadow shadowMapAtlasPoint; - #endif - #else - uniform samplerCubeShadow shadowMapPoint[4]; - #endif - #ifdef _Spot + #ifdef _Clusters + #ifdef _SingleAtlas + //!uniform sampler2DShadow shadowMapAtlas; + #endif + uniform vec2 lightProj; #ifdef _ShadowMapAtlas #ifndef _SingleAtlas - uniform sampler2DShadow shadowMapAtlasSpot; + uniform sampler2DShadow shadowMapAtlasPoint; #endif #else - uniform sampler2DShadow shadowMapSpot[maxLightsCluster]; + uniform samplerCubeShadow shadowMapPoint[4]; + #endif + #ifdef _Spot + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + uniform sampler2DShadow shadowMapAtlasSpot; + #endif + #else + uniform sampler2DShadow shadowMapSpot[maxLightsCluster]; + #endif + uniform mat4 LWVPSpotArray[maxLightsCluster]; #endif - uniform mat4 LWVPSpotArray[maxLightsCluster]; #endif #endif -#endif vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, const vec3 lp, const vec3 lightCol, const vec3 albedo, const float rough, const float spec, const vec3 f0 @@ -102,11 +102,13 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #endif #ifdef _ShadowMap - #ifndef _Spot + if (receiveShadow) { #ifdef _SinglePoint + #ifndef _Spot direct *= PCFCube(shadowMapPoint[0], ld, -l, bias, lightProj, n); #endif + #endif #ifdef _Clusters #ifdef _ShadowMapAtlas direct *= PCFFakeCube( @@ -126,7 +128,6 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #endif } #endif - #endif return direct; } From 7463de61407c9cea2cc972c619c255c8e969145c Mon Sep 17 00:00:00 2001 From: N8n5h Date: Sun, 14 Mar 2021 10:35:25 -0300 Subject: [PATCH 062/264] Add cast_shadow to lightArray data This was done to undo the hardcoded nature of "receiveShadows", which doesn't play nice when you have multiple lights of the same type and one or several have cast_shadow set to false. By simply "passing" the cast_shadow parameter within the lightArray it was possible to detect lights that don't cast shadows before actually wasting gpu cycles to render shadows with garbage data. Support for deferred desktop and mobile, and forward was added. --- Shaders/deferred_light/deferred_light.frag.glsl | 13 +++++++------ .../deferred_light.frag.glsl | 13 +++++++------ .../volumetric_light/volumetric_light.frag.glsl | 2 +- blender/arm/material/make_cluster.py | 16 ++++++++-------- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/Shaders/deferred_light/deferred_light.frag.glsl b/Shaders/deferred_light/deferred_light.frag.glsl index e119349e..fe81fad3 100644 --- a/Shaders/deferred_light/deferred_light.frag.glsl +++ b/Shaders/deferred_light/deferred_light.frag.glsl @@ -94,7 +94,7 @@ uniform vec3 eye; uniform vec3 eyeLook; #ifdef _Clusters -uniform vec4 lightsArray[maxLights * 2]; +uniform vec4 lightsArray[maxLights * 3]; #ifdef _Spot uniform vec4 lightsArraySpot[maxLights]; #endif @@ -456,18 +456,19 @@ void main() { n, v, dotNV, - lightsArray[li * 2].xyz, // lp - lightsArray[li * 2 + 1].xyz, // lightCol + lightsArray[li * 3].xyz, // lp + lightsArray[li * 3 + 1].xyz, // lightCol albedo, roughness, occspec.y, f0 #ifdef _ShadowMap - , li, lightsArray[li * 2].w, true // bias + // light index, shadow bias, cast_shadows + , li, lightsArray[li * 3 + 2].x, lightsArray[li * 3 + 2].z != 0.0 #endif #ifdef _Spot - , lightsArray[li * 2 + 1].w != 0.0 - , lightsArray[li * 2 + 1].w // cutoff + , lightsArray[li * 3 + 2].y != 0.0 + , lightsArray[li * 3 + 2].y // cutoff , lightsArraySpot[li].w // cutoff - exponent , lightsArraySpot[li].xyz // spotDir #endif diff --git a/Shaders/deferred_light_mobile/deferred_light.frag.glsl b/Shaders/deferred_light_mobile/deferred_light.frag.glsl index cc8ce302..de7dceee 100644 --- a/Shaders/deferred_light_mobile/deferred_light.frag.glsl +++ b/Shaders/deferred_light_mobile/deferred_light.frag.glsl @@ -34,7 +34,7 @@ uniform vec3 eye; uniform vec3 eyeLook; #ifdef _Clusters -uniform vec4 lightsArray[maxLights * 2]; +uniform vec4 lightsArray[maxLights * 3]; #ifdef _Spot uniform vec4 lightsArraySpot[maxLights]; #endif @@ -255,18 +255,19 @@ void main() { n, v, dotNV, - lightsArray[li * 2].xyz, // lp - lightsArray[li * 2 + 1].xyz, // lightCol + lightsArray[li * 3].xyz, // lp + lightsArray[li * 3 + 1].xyz, // lightCol albedo, roughness, occspec.y, f0 #ifdef _ShadowMap - , li, lightsArray[li * 2].w, true // bias + // light index, shadow bias, cast_shadows + , li, lightsArray[li * 3 + 2].x, lightsArray[li * 3 + 2].z != 0.0 #endif #ifdef _Spot - , lightsArray[li * 2 + 1].w != 0.0 - , lightsArray[li * 2 + 1].w // cutoff + , lightsArray[li * 3 + 2].y != 0.0 + , lightsArray[li * 3 + 2].y // cutoff , lightsArraySpot[li].w // cutoff - exponent , lightsArraySpot[li].xyz // spotDir #endif diff --git a/Shaders/volumetric_light/volumetric_light.frag.glsl b/Shaders/volumetric_light/volumetric_light.frag.glsl index e1306b06..a0817b54 100644 --- a/Shaders/volumetric_light/volumetric_light.frag.glsl +++ b/Shaders/volumetric_light/volumetric_light.frag.glsl @@ -12,7 +12,7 @@ uniform sampler2D gbufferD; uniform sampler2D snoise; #ifdef _Clusters -uniform vec4 lightsArray[maxLights * 2]; +uniform vec4 lightsArray[maxLights * 3]; #ifdef _Spot uniform vec4 lightsArraySpot[maxLights]; #endif diff --git a/blender/arm/material/make_cluster.py b/blender/arm/material/make_cluster.py index 1ac91d5f..e3d104f1 100644 --- a/blender/arm/material/make_cluster.py +++ b/blender/arm/material/make_cluster.py @@ -9,7 +9,7 @@ def write(vert, frag): frag.add_include_front('std/clusters.glsl') frag.add_uniform('vec2 cameraProj', link='_cameraPlaneProj') frag.add_uniform('vec2 cameraPlane', link='_cameraPlane') - frag.add_uniform('vec4 lightsArray[maxLights * 2]', link='_lightsArray') + frag.add_uniform('vec4 lightsArray[maxLights * 3]', link='_lightsArray') frag.add_uniform('sampler2D clustersData', link='_clustersData') if is_shadows: frag.add_uniform('bool receiveShadow') @@ -56,19 +56,19 @@ def write(vert, frag): frag.write(' n,') frag.write(' vVec,') frag.write(' dotNV,') - frag.write(' lightsArray[li * 2].xyz,') # lp - frag.write(' lightsArray[li * 2 + 1].xyz,') # lightCol + frag.write(' lightsArray[li * 3].xyz,') # lp + frag.write(' lightsArray[li * 3 + 1].xyz,') # lightCol frag.write(' albedo,') frag.write(' roughness,') frag.write(' specular,') frag.write(' f0') if is_shadows: - frag.write(' , li, lightsArray[li * 2].w, receiveShadow') # bias + frag.write('\t, li, lightsArray[li * 3 + 2].x, lightsArray[li * 3 + 2].z != 0.0') # bias if '_Spot' in wrd.world_defs: - frag.write(' , lightsArray[li * 2 + 1].w != 0.0') - frag.write(' , lightsArray[li * 2 + 1].w') # cutoff - frag.write(' , lightsArraySpot[li].w') # cutoff - exponent - frag.write(' , lightsArraySpot[li].xyz') # spotDir + frag.write('\t, lightsArray[li * 3 + 2].y != 0.0') + frag.write('\t, lightsArray[li * 3 + 2].y') # cutoff + frag.write('\t, lightsArraySpot[li].w') # cutoff - exponent + frag.write('\t, lightsArraySpot[li].xyz') # spotDir if '_VoxelShadow' in wrd.world_defs and '_VoxelAOvar' in wrd.world_defs: frag.write(' , voxels, voxpos') frag.write(');') From c7211542b4ee758dbb67f1647e442612ebb81676 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Sun, 14 Mar 2021 10:46:58 -0300 Subject: [PATCH 063/264] Make pointLightData work with lights with cast_shadow=false --- Sources/armory/renderpath/Inc.hx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/armory/renderpath/Inc.hx b/Sources/armory/renderpath/Inc.hx index 940732a3..62109321 100644 --- a/Sources/armory/renderpath/Inc.hx +++ b/Sources/armory/renderpath/Inc.hx @@ -57,7 +57,11 @@ class Inc { break; if (LightObject.discardLightCulled(light)) continue; if (light.data.raw.type == "point") { - for(k in 0...light.tileOffsetX.length) { + if (!light.data.raw.cast_shadow) { + j += 4 * 6; + continue; + } + for(k in 0...6) { LightObject.pointLightsData[j ] = light.tileOffsetX[k]; // posx LightObject.pointLightsData[j + 1] = light.tileOffsetY[k]; // posy LightObject.pointLightsData[j + 2] = light.tileScale[k]; // tile scale factor relative to atlas From 1ad1d564bd46b2398f6226a090f3e4ed2c4f1ed9 Mon Sep 17 00:00:00 2001 From: knowledgenude <63247726+knowledgenude@users.noreply.github.com> Date: Tue, 16 Mar 2021 18:27:42 -0300 Subject: [PATCH 064/264] Fix clamp node --- Sources/armory/logicnode/ClampNode.hx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/armory/logicnode/ClampNode.hx b/Sources/armory/logicnode/ClampNode.hx index 8bd5f13d..911cf9d4 100644 --- a/Sources/armory/logicnode/ClampNode.hx +++ b/Sources/armory/logicnode/ClampNode.hx @@ -1,18 +1,18 @@ package armory.logicnode; +import kha.FastFloat; + class ClampNode extends LogicNode { public function new(tree: LogicTree) { super(tree); } - override function get(from: Int): Dynamic { - var value: kha.FastFloat = inputs[0].get(); - var min: kha.FastFloat = inputs[1].get(); - var max: kha.FastFloat = inputs[2].get(); + override function get(from: Int): FastFloat { + var value = inputs[0].get(); + var min = inputs[1].get(); + var max = inputs[2].get(); - if (value == null || min == null || max == null) return null; - - value <= min ? return min : value >= max ? return max : return value; + return value < min ? min : value > max ? max : value; } } From ca2c3e151a181a33188190da79f46b3a9386a177 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Wed, 17 Mar 2021 20:28:26 -0300 Subject: [PATCH 065/264] Modified UI for collision filter props_collision_filter_mask * Moved to collision filter mask panel under armory props to keep UI more tidy. * Moved the `arm_rb_collision_filter_mask` property out of the props_collision_filter_mask to be with the rest of the properties. * Changed name and description of collision filter mask to be more consistent with the existing rigid body UI. --- blender/arm/props.py | 6 ++++++ blender/arm/props_collision_filter_mask.py | 23 +++++++++++----------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/blender/arm/props.py b/blender/arm/props.py index 72df10b8..91fe1ccf 100755 --- a/blender/arm/props.py +++ b/blender/arm/props.py @@ -280,6 +280,12 @@ def init_properties(): bpy.types.Object.arm_rb_trigger = BoolProperty(name="Trigger", description="Disable contact response", default=False) bpy.types.Object.arm_rb_deactivation_time = FloatProperty(name="Deactivation Time", description="Delay putting rigid body into sleep", default=0.0) bpy.types.Object.arm_rb_ccd = BoolProperty(name="Continuous Collision Detection", description="Improve collision for fast moving objects", default=False) + bpy.types.Object.arm_rb_collision_filter_mask = bpy.props.BoolVectorProperty( + name="Collision Collections Filter Mask", + description="Collision collections rigid body interacts with", + default=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False), + size=20, + subtype='LAYER') bpy.types.Object.arm_animation_enabled = BoolProperty(name="Animation", description="Enable skinning & timeline animation", default=True) bpy.types.Object.arm_tilesheet = StringProperty(name="Tilesheet", description="Set tilesheet animation", default='') bpy.types.Object.arm_tilesheet_action = StringProperty(name="Tilesheet Action", description="Set startup action", default='') diff --git a/blender/arm/props_collision_filter_mask.py b/blender/arm/props_collision_filter_mask.py index d5b52910..00086ac8 100644 --- a/blender/arm/props_collision_filter_mask.py +++ b/blender/arm/props_collision_filter_mask.py @@ -3,29 +3,28 @@ from bpy.props import * from bpy.types import Panel class ARM_PT_RbCollisionFilterMaskPanel(bpy.types.Panel): - bl_label = "Armory Collision Filter Mask" + bl_label = "Collections Filter Mask" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "physics" - + bl_parent_id = "ARM_PT_PhysicsPropsPanel" + + @classmethod + def poll(self, context): + obj = context.object + if obj is None: + return False + return obj.rigid_body is not None 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 - if obj.rigid_body != None: - layout.prop(obj, 'arm_rb_collision_filter_mask') + obj = context.object + layout.prop(obj, 'arm_rb_collision_filter_mask', text="", expand=True) def register(): bpy.utils.register_class(ARM_PT_RbCollisionFilterMaskPanel) - bpy.types.Object.arm_rb_collision_filter_mask = bpy.props.BoolVectorProperty( - name="Collision Filter Mask", - default=(True, False, False,False,False,False, False, False,False,False,False, False, False,False,False,False, False, False,False,False), - size=20, - subtype='LAYER') def unregister(): bpy.utils.unregister_class(ARM_PT_RbCollisionFilterMaskPanel) From ef8fb21536353120495d47115b305a54002e698f Mon Sep 17 00:00:00 2001 From: Alexander Kleemann Date: Thu, 18 Mar 2021 18:49:30 +0100 Subject: [PATCH 066/264] Update lightmapper to Blender 2.9+ Finalized update to support Blender 2.9+ as well as new features, fixes and more stability --- blender/arm/lightmapper/__init__.py | 2 +- .../arm/lightmapper/assets/TLM_Overlay.png | Bin 0 -> 22962 bytes blender/arm/lightmapper/assets/tlm_data.blend | Bin 126952 -> 126639 bytes blender/arm/lightmapper/keymap/__init__.py | 7 - blender/arm/lightmapper/keymap/keymap.py | 2 + blender/arm/lightmapper/operators/__init__.py | 5 +- .../arm/lightmapper/operators/imagetools.py | 172 +++++- .../lightmapper/operators/installopencv.py | 5 +- blender/arm/lightmapper/operators/tlm.py | 436 +++++++++++-- blender/arm/lightmapper/panels/__init__.py | 0 blender/arm/lightmapper/panels/image.py | 66 ++ blender/arm/lightmapper/panels/light.py | 17 + blender/arm/lightmapper/panels/object.py | 118 ++++ blender/arm/lightmapper/panels/scene.py | 582 ++++++++++++++++++ blender/arm/lightmapper/panels/world.py | 17 + .../arm/lightmapper/preferences/__init__.py | 16 + .../preferences/addon_preferences.py | 75 +++ .../arm/lightmapper/properties/__init__.py | 14 +- blender/arm/lightmapper/properties/atlas.py | 34 +- blender/arm/lightmapper/properties/image.py | 26 +- blender/arm/lightmapper/properties/object.py | 17 +- .../lightmapper/properties/renderer/cycles.py | 30 +- .../properties/renderer/octanerender.py | 10 + blender/arm/lightmapper/properties/scene.py | 105 +++- blender/arm/lightmapper/utility/build.py | 415 +++++++++---- .../arm/lightmapper/utility/cycles/cache.py | 95 ++- .../lightmapper/utility/cycles/lightmap.py | 51 +- .../arm/lightmapper/utility/cycles/nodes.py | 113 +++- .../arm/lightmapper/utility/cycles/prepare.py | 385 +++++++----- .../utility/denoiser/integrated.py | 2 +- blender/arm/lightmapper/utility/encoding.py | 237 +++++-- .../lightmapper/utility/filtering/opencv.py | 2 +- .../arm/lightmapper/utility/gui/Viewport.py | 67 ++ .../arm/lightmapper/utility/luxcore/setup.py | 259 ++++++++ .../lightmapper/utility/octane/configure.py | 243 ++++++++ .../lightmapper/utility/octane/lightmap2.py | 71 +++ blender/arm/lightmapper/utility/pack.py | 14 +- blender/arm/lightmapper/utility/utility.py | 146 +++-- blender/arm/props_bake.py | 6 +- blender/arm/props_ui.py | 446 +------------- 40 files changed, 3348 insertions(+), 960 deletions(-) create mode 100644 blender/arm/lightmapper/assets/TLM_Overlay.png create mode 100644 blender/arm/lightmapper/panels/__init__.py create mode 100644 blender/arm/lightmapper/panels/image.py create mode 100644 blender/arm/lightmapper/panels/light.py create mode 100644 blender/arm/lightmapper/panels/object.py create mode 100644 blender/arm/lightmapper/panels/scene.py create mode 100644 blender/arm/lightmapper/panels/world.py create mode 100644 blender/arm/lightmapper/preferences/__init__.py create mode 100644 blender/arm/lightmapper/preferences/addon_preferences.py create mode 100644 blender/arm/lightmapper/utility/gui/Viewport.py create mode 100644 blender/arm/lightmapper/utility/luxcore/setup.py create mode 100644 blender/arm/lightmapper/utility/octane/configure.py create mode 100644 blender/arm/lightmapper/utility/octane/lightmap2.py diff --git a/blender/arm/lightmapper/__init__.py b/blender/arm/lightmapper/__init__.py index eaf15cec..8df717b4 100644 --- a/blender/arm/lightmapper/__init__.py +++ b/blender/arm/lightmapper/__init__.py @@ -1 +1 @@ -__all__ = ('Operators', 'Properties', 'Utility', 'Keymap') \ No newline at end of file +__all__ = ('Operators', 'Panels', 'Properties', 'Preferences', 'Utility', 'Keymap') \ No newline at end of file diff --git a/blender/arm/lightmapper/assets/TLM_Overlay.png b/blender/arm/lightmapper/assets/TLM_Overlay.png new file mode 100644 index 0000000000000000000000000000000000000000..50bef87f4166b81ee1d05a9799ffe32b4904144e GIT binary patch literal 22962 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 Hg@pbO-W@#% literal 0 HcmV?d00001 diff --git a/blender/arm/lightmapper/assets/tlm_data.blend b/blender/arm/lightmapper/assets/tlm_data.blend index 409c894b33630b82edc77541f1c8f99829627679..8dabd2cbe1259c85907a61bc7a600fb494148cdf 100644 GIT binary patch literal 126639 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 z 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

0jVp<00ydE#4~LtO2xDMxVV6yMOtBr+C9A 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 1 or dim[1] > 1: + + if active_image.TLM_ImageProperties.tlm_image_scale_method == "Nearest": + interp = cv2.INTER_NEAREST + elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Area": + interp = cv2.INTER_AREA + elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Linear": + interp = cv2.INTER_LINEAR + elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Cubic": + interp = cv2.INTER_CUBIC + elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Lanczos": + interp = cv2.INTER_LANCZOS4 + + resized = cv2.resize(basefile, dim, interpolation = interp) + + #resizedFile = os.path.join(dir_path, basename + "_" + str(width) + "_" + str(height) + extension) + resizedFile = os.path.join(dir_path, basename + extension) + + cv2.imwrite(resizedFile, resized) + + active_image.filepath_raw = resizedFile + bpy.ops.image.reload() + + print(newfile) + print(img_path) + + else: + + print("Please save image") + + print("Upscale") + + return {'RUNNING_MODAL'} + +class TLM_ImageSwitchUp(bpy.types.Operator): + bl_idname = "tlm.image_switchup" + bl_label = "Quickswitch Up" + bl_description = "Switches to a cached upscaled image" + bl_options = {'REGISTER', 'UNDO'} + + def invoke(self, context, event): + + for area in bpy.context.screen.areas: + if area.type == "IMAGE_EDITOR": + active_image = area.spaces.active.image + + if active_image.source == "FILE": + img_path = active_image.filepath_raw + filename = os.path.basename(img_path) + + print("Switch up") + + return {'RUNNING_MODAL'} + +class TLM_ImageSwitchDown(bpy.types.Operator): + bl_idname = "tlm.image_switchdown" + bl_label = "Quickswitch Down" + bl_description = "Switches to a cached downscaled image" + bl_options = {'REGISTER', 'UNDO'} + + def invoke(self, context, event): + + for area in bpy.context.screen.areas: + if area.type == "IMAGE_EDITOR": + active_image = area.spaces.active.image + + if active_image.source == "FILE": + img_path = active_image.filepath_raw + filename = os.path.basename(img_path) + + print("Switch Down") return {'RUNNING_MODAL'} \ No newline at end of file diff --git a/blender/arm/lightmapper/operators/installopencv.py b/blender/arm/lightmapper/operators/installopencv.py index b3d173a6..21f5e37f 100644 --- a/blender/arm/lightmapper/operators/installopencv.py +++ b/blender/arm/lightmapper/operators/installopencv.py @@ -21,7 +21,10 @@ class TLM_Install_OpenCV(bpy.types.Operator): print("Module OpenCV") - pythonbinpath = bpy.app.binary_path_python + if (2, 91, 0) > bpy.app.version: + pythonbinpath = bpy.app.binary_path_python + else: + pythonbinpath = sys.executable if platform.system() == "Windows": pythonlibpath = os.path.join(os.path.dirname(os.path.dirname(pythonbinpath)), "lib") diff --git a/blender/arm/lightmapper/operators/tlm.py b/blender/arm/lightmapper/operators/tlm.py index ace239d7..a46f3604 100644 --- a/blender/arm/lightmapper/operators/tlm.py +++ b/blender/arm/lightmapper/operators/tlm.py @@ -1,9 +1,36 @@ -import bpy, os, time, blf, webbrowser, platform +import bpy, os, time, blf, webbrowser, platform, numpy, bmesh import math, subprocess, multiprocessing +from .. utility import utility from .. utility import build from .. utility.cycles import cache from .. network import server +def setObjectLightmapByWeight(minimumRes, maximumRes, objWeight): + + availableResolutions = [32,64,128,256,512,1024,2048,4096,8192] + + minRes = minimumRes + minResIdx = availableResolutions.index(minRes) + maxRes = maximumRes + maxResIdx = availableResolutions.index(maxRes) + + exampleWeight = objWeight + + if minResIdx == maxResIdx: + pass + else: + + increment = 1.0/(maxResIdx-minResIdx) + + assortedRange = [] + + for a in numpy.arange(0.0, 1.0, increment): + assortedRange.append(round(a, 2)) + + assortedRange.append(1.0) + nearestWeight = min(assortedRange, key=lambda x:abs(x - exampleWeight)) + return (availableResolutions[assortedRange.index(nearestWeight) + minResIdx]) + class TLM_BuildLightmaps(bpy.types.Operator): bl_idname = "tlm.build_lightmaps" bl_label = "Build Lightmaps" @@ -52,13 +79,13 @@ class TLM_CleanLightmaps(bpy.types.Operator): for file in os.listdir(dirpath): os.remove(os.path.join(dirpath + "/" + file)) - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: cache.backup_material_restore(obj) - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: cache.backup_material_rename(obj) @@ -75,8 +102,8 @@ class TLM_CleanLightmaps(bpy.types.Operator): if image.name.endswith("_baked"): bpy.data.images.remove(image, do_unlink=True) - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: if obj.TLM_ObjectProperties.tlm_postpack_object: @@ -92,14 +119,17 @@ class TLM_CleanLightmaps(bpy.types.Operator): bpy.ops.object.select_all(action='DESELECT') obj.select_set(True) bpy.context.view_layer.objects.active = obj - #print(x) uv_layers = obj.data.uv_layers + + if not obj.TLM_ObjectProperties.tlm_use_default_channel: + uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel + else: + uv_channel = "UVMap_Lightmap" + for i in range(0, len(uv_layers)): - if uv_layers[i].name == 'UVMap_Lightmap': + if uv_layers[i].name == uv_channel: uv_layers.active_index = i - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Lightmap shift A") break bpy.ops.object.mode_set(mode='EDIT') @@ -111,9 +141,11 @@ class TLM_CleanLightmaps(bpy.types.Operator): bpy.ops.object.mode_set(mode='OBJECT') if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - #print(obj.name + ": Active UV: " + obj.data.uv_layers[obj.data.uv_layers.active_index].name) print("Resized for obj: " + obj.name) + if "Lightmap" in obj: + del obj["Lightmap"] + return {'FINISHED'} class TLM_ExploreLightmaps(bpy.types.Operator): @@ -153,63 +185,285 @@ class TLM_ExploreLightmaps(bpy.types.Operator): return {'FINISHED'} -class TLM_EnableSelection(bpy.types.Operator): - """Enable for selection""" - bl_idname = "tlm.enable_selection" - bl_label = "Enable for selection" - bl_description = "Enable for selection" +class TLM_EnableSet(bpy.types.Operator): + """Enable for set""" + bl_idname = "tlm.enable_set" + bl_label = "Enable for set" + bl_description = "Enable for set" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): scene = context.scene - for obj in bpy.context.selected_objects: - obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = True + weightList = {} #ObjName : [Dimension,Weight] + max = 0 - if scene.TLM_SceneProperties.tlm_override_object_settings: - obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = scene.TLM_SceneProperties.tlm_mesh_lightmap_resolution - obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode = scene.TLM_SceneProperties.tlm_mesh_lightmap_unwrap_mode - obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin = scene.TLM_SceneProperties.tlm_mesh_unwrap_margin - obj.TLM_ObjectProperties.tlm_postpack_object = scene.TLM_SceneProperties.tlm_postpack_object + if bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Scene": + for obj in bpy.context.scene.objects: + if obj.type == "MESH": - if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": - obj.TLM_ObjectProperties.tlm_atlas_pointer = scene.TLM_SceneProperties.tlm_atlas_pointer + print("Enabling for scene: " + obj.name) + + bpy.context.view_layer.objects.active = obj + obj.select_set(True) + bpy.ops.object.transform_apply(location=False, rotation=True, scale=True) + + obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = True + + obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode = bpy.context.scene.TLM_SceneProperties.tlm_mesh_lightmap_unwrap_mode + obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin = bpy.context.scene.TLM_SceneProperties.tlm_mesh_unwrap_margin + obj.TLM_ObjectProperties.tlm_postpack_object = bpy.context.scene.TLM_SceneProperties.tlm_postpack_object - obj.TLM_ObjectProperties.tlm_postatlas_pointer = scene.TLM_SceneProperties.tlm_postatlas_pointer + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": + obj.TLM_ObjectProperties.tlm_atlas_pointer = bpy.context.scene.TLM_SceneProperties.tlm_atlas_pointer + obj.TLM_ObjectProperties.tlm_postatlas_pointer = bpy.context.scene.TLM_SceneProperties.tlm_postatlas_pointer + + if bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Single": + obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = scene.TLM_SceneProperties.tlm_mesh_lightmap_resolution + elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Dimension": + obj_dimensions = obj.dimensions.x * obj.dimensions.y * obj.dimensions.z + weightList[obj.name] = [obj_dimensions, 0] + if obj_dimensions > max: + max = obj_dimensions + elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Surface": + bm = bmesh.new() + bm.from_mesh(obj.data) + area = sum(f.calc_area() for f in bm.faces) + weightList[obj.name] = [area, 0] + if area > max: + max = area + elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Volume": + bm = bmesh.new() + bm.from_mesh(obj.data) + volume = float( bm.calc_volume()) + weightList[obj.name] = [volume, 0] + if volume > max: + max = volume + + elif bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Selection": + for obj in bpy.context.selected_objects: + if obj.type == "MESH": + + print("Enabling for selection: " + obj.name) + + bpy.context.view_layer.objects.active = obj + obj.select_set(True) + bpy.ops.object.transform_apply(location=False, rotation=True, scale=True) + + obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = True + + obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode = bpy.context.scene.TLM_SceneProperties.tlm_mesh_lightmap_unwrap_mode + obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin = bpy.context.scene.TLM_SceneProperties.tlm_mesh_unwrap_margin + obj.TLM_ObjectProperties.tlm_postpack_object = bpy.context.scene.TLM_SceneProperties.tlm_postpack_object + + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": + obj.TLM_ObjectProperties.tlm_atlas_pointer = bpy.context.scene.TLM_SceneProperties.tlm_atlas_pointer + + obj.TLM_ObjectProperties.tlm_postatlas_pointer = bpy.context.scene.TLM_SceneProperties.tlm_postatlas_pointer + + if bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Single": + obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = scene.TLM_SceneProperties.tlm_mesh_lightmap_resolution + elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Dimension": + obj_dimensions = obj.dimensions.x * obj.dimensions.y * obj.dimensions.z + weightList[obj.name] = [obj_dimensions, 0] + if obj_dimensions > max: + max = obj_dimensions + elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Surface": + bm = bmesh.new() + bm.from_mesh(obj.data) + area = sum(f.calc_area() for f in bm.faces) + weightList[obj.name] = [area, 0] + if area > max: + max = area + elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Volume": + bm = bmesh.new() + bm.from_mesh(obj.data) + volume = float( bm.calc_volume()) + weightList[obj.name] = [volume, 0] + if volume > max: + max = volume + + else: #Enabled + for obj in bpy.context.scene.objects: + if obj.type == "MESH": + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + + print("Enabling for designated: " + obj.name) + + bpy.context.view_layer.objects.active = obj + obj.select_set(True) + bpy.ops.object.transform_apply(location=False, rotation=True, scale=True) + + obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = True + + obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode = bpy.context.scene.TLM_SceneProperties.tlm_mesh_lightmap_unwrap_mode + obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin = bpy.context.scene.TLM_SceneProperties.tlm_mesh_unwrap_margin + obj.TLM_ObjectProperties.tlm_postpack_object = bpy.context.scene.TLM_SceneProperties.tlm_postpack_object + + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": + obj.TLM_ObjectProperties.tlm_atlas_pointer = bpy.context.scene.TLM_SceneProperties.tlm_atlas_pointer + + obj.TLM_ObjectProperties.tlm_postatlas_pointer = bpy.context.scene.TLM_SceneProperties.tlm_postatlas_pointer + + if bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Single": + obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = scene.TLM_SceneProperties.tlm_mesh_lightmap_resolution + elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Dimension": + obj_dimensions = obj.dimensions.x * obj.dimensions.y * obj.dimensions.z + weightList[obj.name] = [obj_dimensions, 0] + if obj_dimensions > max: + max = obj_dimensions + elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Surface": + bm = bmesh.new() + bm.from_mesh(obj.data) + area = sum(f.calc_area() for f in bm.faces) + weightList[obj.name] = [area, 0] + if area > max: + max = area + elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Volume": + bm = bmesh.new() + bm.from_mesh(obj.data) + volume = float( bm.calc_volume()) + weightList[obj.name] = [volume, 0] + if volume > max: + max = volume + + if bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Scene": + for obj in bpy.context.scene.objects: + if obj.type == "MESH": + + if bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight != "Single": + for key in weightList: + weightList[obj.name][1] = weightList[obj.name][0] / max + a = setObjectLightmapByWeight(int(bpy.context.scene.TLM_SceneProperties.tlm_resolution_min), int(bpy.context.scene.TLM_SceneProperties.tlm_resolution_max), weightList[obj.name][1]) + print(str(a) + "/" + str(weightList[obj.name][1])) + print("Scale: " + str(weightList[obj.name][0])) + print("Obj: " + obj.name) + obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = str(a) + + elif bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Selection": + for obj in bpy.context.selected_objects: + if obj.type == "MESH": + + if bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight != "Single": + for key in weightList: + weightList[obj.name][1] = weightList[obj.name][0] / max + a = setObjectLightmapByWeight(int(bpy.context.scene.TLM_SceneProperties.tlm_resolution_min), int(bpy.context.scene.TLM_SceneProperties.tlm_resolution_max), weightList[obj.name][1]) + print(str(a) + "/" + str(weightList[obj.name][1])) + print("Scale: " + str(weightList[obj.name][0])) + print("Obj: " + obj.name) + obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = str(a) + + + else: #Enabled + for obj in bpy.context.scene.objects: + if obj.type == "MESH": + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + + if bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight != "Single": + for key in weightList: + weightList[obj.name][1] = weightList[obj.name][0] / max + a = setObjectLightmapByWeight(int(bpy.context.scene.TLM_SceneProperties.tlm_resolution_min), int(bpy.context.scene.TLM_SceneProperties.tlm_resolution_max), weightList[obj.name][1]) + print(str(a) + "/" + str(weightList[obj.name][1])) + print("Scale: " + str(weightList[obj.name][0])) + print("Obj: " + obj.name) + print("") + obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = str(a) + return{'FINISHED'} class TLM_DisableSelection(bpy.types.Operator): - """Disable for selection""" + """Disable for set""" bl_idname = "tlm.disable_selection" - bl_label = "Disable for selection" + bl_label = "Disable for set" bl_description = "Disable for selection" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): - for obj in bpy.context.selected_objects: - obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = False + scene = context.scene + + weightList = {} #ObjName : [Dimension,Weight] + max = 0 + + if bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Scene": + for obj in bpy.context.scene.objects: + if obj.type == "MESH": + + obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = False + + elif bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Selection": + for obj in bpy.context.selected_objects: + if obj.type == "MESH": + + obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = False + + + else: #Enabled + for obj in bpy.context.scene.objects: + if obj.type == "MESH": + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + + obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = False + return{'FINISHED'} class TLM_RemoveLightmapUV(bpy.types.Operator): - """Remove Lightmap UV for selection""" + """Remove Lightmap UV for set""" bl_idname = "tlm.remove_uv_selection" bl_label = "Remove Lightmap UV" - bl_description = "Remove Lightmap UV for selection" + bl_description = "Remove Lightmap UV for set" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): - for obj in bpy.context.selected_objects: - if obj.type == "MESH": - uv_layers = obj.data.uv_layers + if bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Scene": + for obj in bpy.context.scene.objects: + if obj.type == "MESH": - for uvlayer in uv_layers: - if uvlayer.name == "UVMap_Lightmap": - uv_layers.remove(uvlayer) + uv_layers = obj.data.uv_layers + + if not obj.TLM_ObjectProperties.tlm_use_default_channel: + uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel + else: + uv_channel = "UVMap_Lightmap" + + for uvlayer in uv_layers: + if uvlayer.name == uv_channel: + uv_layers.remove(uvlayer) + + elif bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Selection": + for obj in bpy.context.selected_objects: + if obj.type == "MESH": + + uv_layers = obj.data.uv_layers + + if not obj.TLM_ObjectProperties.tlm_use_default_channel: + uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel + else: + uv_channel = "UVMap_Lightmap" + + for uvlayer in uv_layers: + if uvlayer.name == uv_channel: + uv_layers.remove(uvlayer) + + else: #Enabled + for obj in bpy.context.scene.objects: + if obj.type == "MESH": + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + + uv_layers = obj.data.uv_layers + + if not obj.TLM_ObjectProperties.tlm_use_default_channel: + uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel + else: + uv_channel = "UVMap_Lightmap" + + for uvlayer in uv_layers: + if uvlayer.name == uv_channel: + uv_layers.remove(uvlayer) return{'FINISHED'} @@ -222,8 +476,8 @@ class TLM_SelectLightmapped(bpy.types.Operator): def execute(self, context): - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: obj.select_set(True) @@ -278,7 +532,7 @@ class TLM_AtlastListDeleteItem(bpy.types.Operator): list = scene.TLM_AtlasList index = scene.TLM_AtlasListItem - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: atlasName = scene.TLM_AtlasList[index].name @@ -310,7 +564,7 @@ class TLM_PostAtlastListDeleteItem(bpy.types.Operator): list = scene.TLM_PostAtlasList index = scene.TLM_PostAtlasListItem - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: atlasName = scene.TLM_PostAtlasList[index].name @@ -437,7 +691,7 @@ class TLM_BuildEnvironmentProbes(bpy.types.Operator): def invoke(self, context, event): - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: if obj.type == "LIGHT_PROBE": if obj.data.type == "CUBEMAP": @@ -500,7 +754,7 @@ class TLM_BuildEnvironmentProbes(bpy.types.Operator): cam.rotation_euler = positions[val] filename = os.path.join(directory, val) + "_" + camobj_name + ".hdr" - bpy.data.scenes['Scene'].render.filepath = filename + bpy.context.scene.render.filepath = filename print("Writing out: " + val) bpy.ops.render.render(write_still=True) @@ -642,7 +896,7 @@ class TLM_BuildEnvironmentProbes(bpy.types.Operator): subprocess.call([envpipe3], shell=True) - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: obj.select_set(False) cam_obj.select_set(True) @@ -686,7 +940,92 @@ class TLM_MergeAdjacentActors(bpy.types.Operator): scene = context.scene - + return {'FINISHED'} + +class TLM_PrepareUVMaps(bpy.types.Operator): + bl_idname = "tlm.prepare_uvmaps" + bl_label = "Prepare UV maps" + bl_description = "Prepare UV lightmaps for selected objects" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + + scene = context.scene + + + + return {'FINISHED'} + +class TLM_LoadLightmaps(bpy.types.Operator): + bl_idname = "tlm.load_lightmaps" + bl_label = "Load Lightmaps" + bl_description = "Load lightmaps from selected folder" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + + scene = context.scene + + utility.transfer_load() + + build.finish_assemble() + + return {'FINISHED'} + +class TLM_ToggleTexelDensity(bpy.types.Operator): + bl_idname = "tlm.toggle_texel_density" + bl_label = "Toggle Texel Density" + bl_description = "Toggle visualize lightmap texel density for selected objects" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + + scene = context.scene + + for obj in bpy.context.selected_objects: + if obj.type == "MESH": + uv_layers = obj.data.uv_layers + + #if the object has a td_vis in the uv maps, toggle off + #else toggle on + + if obj.TLM_ObjectProperties.tlm_use_default_channel: + + for i in range(0, len(uv_layers)): + if uv_layers[i].name == 'UVMap_Lightmap': + uv_layers.active_index = i + break + else: + + for i in range(0, len(uv_layers)): + if uv_layers[i].name == obj.TLM_ObjectProperties.tlm_uv_channel: + uv_layers.active_index = i + break + + #filepath = r"C:\path\to\image.png" + + #img = bpy.data.images.load(filepath) + + for area in bpy.context.screen.areas: + if area.type == 'VIEW_3D': + space_data = area.spaces.active + bpy.ops.screen.area_dupli('INVOKE_DEFAULT') + new_window = context.window_manager.windows[-1] + + area = new_window.screen.areas[-1] + area.type = 'VIEW_3D' + #bg = space_data.background_images.new() + print(bpy.context.object) + bpy.ops.object.bake_td_uv_to_vc() + + #bg.image = img + break + + + #set active uv_layer to + + + print("TLM_Viz_Toggle") return {'FINISHED'} @@ -698,7 +1037,4 @@ def TLM_HalfResolution(): pass def TLM_DivideLMGroups(): - pass - -def TLM_LoadFromFolder(): pass \ No newline at end of file diff --git a/blender/arm/lightmapper/panels/__init__.py b/blender/arm/lightmapper/panels/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/blender/arm/lightmapper/panels/image.py b/blender/arm/lightmapper/panels/image.py new file mode 100644 index 00000000..929907e2 --- /dev/null +++ b/blender/arm/lightmapper/panels/image.py @@ -0,0 +1,66 @@ +import bpy, os, math, importlib + +from bpy.types import Menu, Operator, Panel, UIList + +from bpy.props import ( + StringProperty, + BoolProperty, + IntProperty, + FloatProperty, + FloatVectorProperty, + EnumProperty, + PointerProperty, +) + +class TLM_PT_Imagetools(bpy.types.Panel): + bl_label = "TLM Imagetools" + bl_space_type = "IMAGE_EDITOR" + bl_region_type = 'UI' + bl_category = "TLM Imagetools" + + def draw_header(self, _): + layout = self.layout + row = layout.row(align=True) + row.label(text ="Image Tools") + + def draw(self, context): + layout = self.layout + + activeImg = None + + for area in bpy.context.screen.areas: + if area.type == 'IMAGE_EDITOR': + activeImg = area.spaces.active.image + + if activeImg is not None and activeImg.name != "Render Result" and activeImg.name != "Viewer Node": + + cv2 = importlib.util.find_spec("cv2") + + if cv2 is None: + row = layout.row(align=True) + row.label(text ="OpenCV not installed.") + else: + + row = layout.row(align=True) + row.label(text ="Method") + row = layout.row(align=True) + row.prop(activeImg.TLM_ImageProperties, "tlm_image_scale_engine") + row = layout.row(align=True) + row.prop(activeImg.TLM_ImageProperties, "tlm_image_cache_switch") + row = layout.row(align=True) + row.operator("tlm.image_upscale") + if activeImg.TLM_ImageProperties.tlm_image_cache_switch: + row = layout.row(align=True) + row.label(text ="Switch up.") + row = layout.row(align=True) + row.operator("tlm.image_downscale") + if activeImg.TLM_ImageProperties.tlm_image_cache_switch: + row = layout.row(align=True) + row.label(text ="Switch down.") + if activeImg.TLM_ImageProperties.tlm_image_scale_engine == "OpenCV": + row = layout.row(align=True) + row.prop(activeImg.TLM_ImageProperties, "tlm_image_scale_method") + + else: + row = layout.row(align=True) + row.label(text ="Select an image") \ No newline at end of file diff --git a/blender/arm/lightmapper/panels/light.py b/blender/arm/lightmapper/panels/light.py new file mode 100644 index 00000000..fd576af1 --- /dev/null +++ b/blender/arm/lightmapper/panels/light.py @@ -0,0 +1,17 @@ +import bpy +from bpy.props import * +from bpy.types import Menu, Panel + +class TLM_PT_LightMenu(bpy.types.Panel): + bl_label = "The Lightmapper" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "light" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + layout = self.layout + scene = context.scene + obj = bpy.context.object + layout.use_property_split = True + layout.use_property_decorate = False \ No newline at end of file diff --git a/blender/arm/lightmapper/panels/object.py b/blender/arm/lightmapper/panels/object.py new file mode 100644 index 00000000..7f0e7abe --- /dev/null +++ b/blender/arm/lightmapper/panels/object.py @@ -0,0 +1,118 @@ +import bpy +from bpy.props import * +from bpy.types import Menu, Panel + +class TLM_PT_ObjectMenu(bpy.types.Panel): + bl_label = "The Lightmapper" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "object" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + layout = self.layout + scene = context.scene + obj = bpy.context.object + layout.use_property_split = True + layout.use_property_decorate = False + + if obj.type == "MESH": + row = layout.row(align=True) + 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 TLM_PT_MaterialMenu(bpy.types.Panel): + bl_label = "The Lightmapper" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "material" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + layout = self.layout + scene = context.scene + obj = bpy.context.object + layout.use_property_split = True + layout.use_property_decorate = False + + mat = bpy.context.material + if mat == None: + return + + if obj.type == "MESH": + + row = layout.row() + row.prop(mat, "TLM_ignore") \ No newline at end of file diff --git a/blender/arm/lightmapper/panels/scene.py b/blender/arm/lightmapper/panels/scene.py new file mode 100644 index 00000000..56c05cb7 --- /dev/null +++ b/blender/arm/lightmapper/panels/scene.py @@ -0,0 +1,582 @@ +import bpy, importlib, math +from bpy.props import * +from bpy.types import Menu, Panel +from .. utility import icon +from .. properties.denoiser import oidn, optix + +class TLM_PT_Settings(bpy.types.Panel): + bl_label = "Settings" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "render" + bl_options = {'DEFAULT_CLOSED'} + bl_parent_id = "ARM_PT_BakePanel" + + @classmethod + def poll(self, context): + scene = context.scene + return scene.arm_bakemode == "Lightmap" + + def draw(self, context): + layout = self.layout + scene = context.scene + layout.use_property_split = True + layout.use_property_decorate = False + sceneProperties = scene.TLM_SceneProperties + + row = layout.row(align=True) + + #We list LuxCoreRender as available, by default we assume Cycles exists + row.prop(sceneProperties, "tlm_lightmap_engine") + + if sceneProperties.tlm_lightmap_engine == "Cycles": + + #CYCLES SETTINGS HERE + engineProperties = scene.TLM_EngineProperties + + row = layout.row(align=True) + row.label(text="General Settings") + row = layout.row(align=True) + row.operator("tlm.build_lightmaps") + row = layout.row(align=True) + row.operator("tlm.clean_lightmaps") + row = layout.row(align=True) + row.operator("tlm.explore_lightmaps") + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_apply_on_unwrap") + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_headless") + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_alert_on_finish") + + if sceneProperties.tlm_alert_on_finish: + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_alert_sound") + + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_verbose") + #row = layout.row(align=True) + #row.prop(sceneProperties, "tlm_compile_statistics") + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_override_bg_color") + if sceneProperties.tlm_override_bg_color: + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_override_color") + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_reset_uv") + + row = layout.row(align=True) + try: + if bpy.context.scene["TLM_Buildstat"] is not None: + row.label(text="Last build completed in: " + str(bpy.context.scene["TLM_Buildstat"][0])) + except: + pass + + row = layout.row(align=True) + row.label(text="Cycles Settings") + + row = layout.row(align=True) + row.prop(engineProperties, "tlm_mode") + row = layout.row(align=True) + row.prop(engineProperties, "tlm_quality") + row = layout.row(align=True) + row.prop(engineProperties, "tlm_resolution_scale") + row = layout.row(align=True) + row.prop(engineProperties, "tlm_bake_mode") + row = layout.row(align=True) + row.prop(engineProperties, "tlm_target") + row = layout.row(align=True) + row.prop(engineProperties, "tlm_lighting_mode") + # if scene.TLM_EngineProperties.tlm_lighting_mode == "combinedao" or scene.TLM_EngineProperties.tlm_lighting_mode == "indirectao": + # row = layout.row(align=True) + # row.prop(engineProperties, "tlm_premultiply_ao") + if scene.TLM_EngineProperties.tlm_bake_mode == "Background": + row = layout.row(align=True) + row.label(text="Warning! Background mode is currently unstable", icon_value=2) + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_network_render") + if sceneProperties.tlm_network_render: + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_network_paths") + #row = layout.row(align=True) + #row.prop(sceneProperties, "tlm_network_dir") + row = layout.row(align=True) + row.prop(engineProperties, "tlm_caching_mode") + row = layout.row(align=True) + row.prop(engineProperties, "tlm_directional_mode") + row = layout.row(align=True) + row.prop(engineProperties, "tlm_lightmap_savedir") + row = layout.row(align=True) + row.prop(engineProperties, "tlm_dilation_margin") + row = layout.row(align=True) + row.prop(engineProperties, "tlm_exposure_multiplier") + row = layout.row(align=True) + row.prop(engineProperties, "tlm_setting_supersample") + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_metallic_clamp") + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_texture_interpolation") + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_texture_extrapolation") + + + + # elif sceneProperties.tlm_lightmap_engine == "LuxCoreRender": + + # engineProperties = scene.TLM_Engine2Properties + # row = layout.row(align=True) + # row.prop(engineProperties, "tlm_luxcore_dir") + # row = layout.row(align=True) + # row.operator("tlm.build_lightmaps") + # #LUXCORE SETTINGS HERE + # #luxcore_available = False + + # #Look for Luxcorerender in the renderengine classes + # # for engine in bpy.types.RenderEngine.__subclasses__(): + # # if engine.bl_idname == "LUXCORE": + # # luxcore_available = True + # # break + + # # row = layout.row(align=True) + # # if not luxcore_available: + # # row.label(text="Please install BlendLuxCore.") + # # else: + # # row.label(text="LuxCoreRender not yet available.") + + elif sceneProperties.tlm_lightmap_engine == "OctaneRender": + + engineProperties = scene.TLM_Engine3Properties + + #LUXCORE SETTINGS HERE + octane_available = True + + + + row = layout.row(align=True) + row.operator("tlm.build_lightmaps") + row = layout.row(align=True) + row.operator("tlm.clean_lightmaps") + row = layout.row(align=True) + row.operator("tlm.explore_lightmaps") + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_verbose") + row = layout.row(align=True) + row.prop(engineProperties, "tlm_lightmap_savedir") + row = layout.row(align=True) + +class TLM_PT_Denoise(bpy.types.Panel): + bl_label = "Denoise" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "render" + bl_options = {'DEFAULT_CLOSED'} + bl_parent_id = "ARM_PT_BakePanel" + + @classmethod + def poll(self, context): + scene = context.scene + return scene.arm_bakemode == "Lightmap" + + def draw_header(self, context): + scene = context.scene + sceneProperties = scene.TLM_SceneProperties + self.layout.prop(sceneProperties, "tlm_denoise_use", text="") + + def draw(self, context): + layout = self.layout + scene = context.scene + layout.use_property_split = True + layout.use_property_decorate = False + sceneProperties = scene.TLM_SceneProperties + layout.active = sceneProperties.tlm_denoise_use + + row = layout.row(align=True) + + #row.prop(sceneProperties, "tlm_denoiser", expand=True) + #row = layout.row(align=True) + row.prop(sceneProperties, "tlm_denoise_engine", expand=True) + row = layout.row(align=True) + + if sceneProperties.tlm_denoise_engine == "Integrated": + row.label(text="No options for Integrated.") + elif sceneProperties.tlm_denoise_engine == "OIDN": + denoiseProperties = scene.TLM_OIDNEngineProperties + row.prop(denoiseProperties, "tlm_oidn_path") + row = layout.row(align=True) + row.prop(denoiseProperties, "tlm_oidn_verbose") + row = layout.row(align=True) + row.prop(denoiseProperties, "tlm_oidn_threads") + row = layout.row(align=True) + row.prop(denoiseProperties, "tlm_oidn_maxmem") + row = layout.row(align=True) + row.prop(denoiseProperties, "tlm_oidn_affinity") + # row = layout.row(align=True) + # row.prop(denoiseProperties, "tlm_denoise_ao") + elif sceneProperties.tlm_denoise_engine == "Optix": + denoiseProperties = scene.TLM_OptixEngineProperties + row.prop(denoiseProperties, "tlm_optix_path") + row = layout.row(align=True) + row.prop(denoiseProperties, "tlm_optix_verbose") + row = layout.row(align=True) + row.prop(denoiseProperties, "tlm_optix_maxmem") + #row = layout.row(align=True) + #row.prop(denoiseProperties, "tlm_denoise_ao") + +class TLM_PT_Filtering(bpy.types.Panel): + bl_label = "Filtering" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "render" + bl_options = {'DEFAULT_CLOSED'} + bl_parent_id = "ARM_PT_BakePanel" + + @classmethod + def poll(self, context): + scene = context.scene + return scene.arm_bakemode == "Lightmap" + + def draw_header(self, context): + scene = context.scene + sceneProperties = scene.TLM_SceneProperties + self.layout.prop(sceneProperties, "tlm_filtering_use", text="") + + def draw(self, context): + layout = self.layout + scene = context.scene + layout.use_property_split = True + layout.use_property_decorate = False + sceneProperties = scene.TLM_SceneProperties + layout.active = sceneProperties.tlm_filtering_use + #row = layout.row(align=True) + #row.label(text="TODO MAKE CHECK") + #row = layout.row(align=True) + #row.prop(sceneProperties, "tlm_filtering_engine", expand=True) + row = layout.row(align=True) + + if sceneProperties.tlm_filtering_engine == "OpenCV": + + cv2 = importlib.util.find_spec("cv2") + + if cv2 is None: + row = layout.row(align=True) + row.label(text="OpenCV is not installed. Install it through preferences.") + else: + row = layout.row(align=True) + row.prop(scene.TLM_SceneProperties, "tlm_filtering_mode") + row = layout.row(align=True) + if scene.TLM_SceneProperties.tlm_filtering_mode == "Gaussian": + row.prop(scene.TLM_SceneProperties, "tlm_filtering_gaussian_strength") + row = layout.row(align=True) + row.prop(scene.TLM_SceneProperties, "tlm_filtering_iterations") + elif scene.TLM_SceneProperties.tlm_filtering_mode == "Box": + row.prop(scene.TLM_SceneProperties, "tlm_filtering_box_strength") + row = layout.row(align=True) + row.prop(scene.TLM_SceneProperties, "tlm_filtering_iterations") + + elif scene.TLM_SceneProperties.tlm_filtering_mode == "Bilateral": + row.prop(scene.TLM_SceneProperties, "tlm_filtering_bilateral_diameter") + row = layout.row(align=True) + row.prop(scene.TLM_SceneProperties, "tlm_filtering_bilateral_color_deviation") + row = layout.row(align=True) + row.prop(scene.TLM_SceneProperties, "tlm_filtering_bilateral_coordinate_deviation") + row = layout.row(align=True) + row.prop(scene.TLM_SceneProperties, "tlm_filtering_iterations") + else: + row.prop(scene.TLM_SceneProperties, "tlm_filtering_median_kernel", expand=True) + row = layout.row(align=True) + row.prop(scene.TLM_SceneProperties, "tlm_filtering_iterations") + else: + row = layout.row(align=True) + row.prop(scene.TLM_SceneProperties, "tlm_numpy_filtering_mode") + + +class TLM_PT_Encoding(bpy.types.Panel): + bl_label = "Encoding" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "render" + bl_options = {'DEFAULT_CLOSED'} + bl_parent_id = "ARM_PT_BakePanel" + + @classmethod + def poll(self, context): + scene = context.scene + return scene.arm_bakemode == "Lightmap" + + def draw_header(self, context): + scene = context.scene + sceneProperties = scene.TLM_SceneProperties + self.layout.prop(sceneProperties, "tlm_encoding_use", text="") + + def draw(self, context): + layout = self.layout + scene = context.scene + layout.use_property_split = True + layout.use_property_decorate = False + sceneProperties = scene.TLM_SceneProperties + layout.active = sceneProperties.tlm_encoding_use + + sceneProperties = scene.TLM_SceneProperties + row = layout.row(align=True) + + if scene.TLM_EngineProperties.tlm_bake_mode == "Background": + row.label(text="Encoding options disabled in background mode") + row = layout.row(align=True) + + else: + + row.prop(sceneProperties, "tlm_encoding_device", expand=True) + row = layout.row(align=True) + + if sceneProperties.tlm_encoding_device == "CPU": + row.prop(sceneProperties, "tlm_encoding_mode_a", expand=True) + else: + row.prop(sceneProperties, "tlm_encoding_mode_b", expand=True) + + if sceneProperties.tlm_encoding_device == "CPU": + if sceneProperties.tlm_encoding_mode_a == "RGBM": + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_encoding_range") + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_decoder_setup") + if sceneProperties.tlm_encoding_mode_a == "RGBD": + pass + if sceneProperties.tlm_encoding_mode_a == "HDR": + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_format") + else: + + if sceneProperties.tlm_encoding_mode_b == "RGBM": + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_encoding_range") + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_decoder_setup") + + if sceneProperties.tlm_encoding_mode_b == "LogLuv" and sceneProperties.tlm_encoding_device == "GPU": + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_decoder_setup") + if sceneProperties.tlm_encoding_mode_b == "HDR": + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_format") + +class TLM_PT_Utility(bpy.types.Panel): + bl_label = "Utilities" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "render" + bl_options = {'DEFAULT_CLOSED'} + bl_parent_id = "ARM_PT_BakePanel" + + @classmethod + def poll(self, context): + scene = context.scene + return scene.arm_bakemode == "Lightmap" + + def draw(self, context): + layout = self.layout + scene = context.scene + layout.use_property_split = True + layout.use_property_decorate = False + sceneProperties = scene.TLM_SceneProperties + + row = layout.row(align=True) + row.label(text="Enable Lightmaps for set") + row = layout.row(align=True) + row.operator("tlm.enable_set") + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_utility_set") + row = layout.row(align=True) + #row.label(text="ABCD") + row.prop(sceneProperties, "tlm_mesh_lightmap_unwrap_mode") + + if sceneProperties.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(sceneProperties, "tlm_atlas_pointer", scene, "TLM_AtlasList", text='Atlas Group') + else: + row = layout.label(text="Add Atlas Groups from the scene lightmapping settings.") + + else: + + row = layout.row() + row.prop(sceneProperties, "tlm_postpack_object") + row = layout.row() + + if sceneProperties.tlm_postpack_object and sceneProperties.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(sceneProperties, "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(sceneProperties, "tlm_mesh_unwrap_margin") + row = layout.row() + row.prop(sceneProperties, "tlm_resolution_weight") + + if sceneProperties.tlm_resolution_weight == "Single": + row = layout.row() + row.prop(sceneProperties, "tlm_mesh_lightmap_resolution") + else: + row = layout.row() + row.prop(sceneProperties, "tlm_resolution_min") + row = layout.row() + row.prop(sceneProperties, "tlm_resolution_max") + + row = layout.row() + row.operator("tlm.disable_selection") + row = layout.row(align=True) + row.operator("tlm.select_lightmapped_objects") + row = layout.row(align=True) + row.operator("tlm.remove_uv_selection") + row = layout.row(align=True) + + + row.label(text="Environment Probes") + row = layout.row() + row.operator("tlm.build_environmentprobe") + row = layout.row() + row.operator("tlm.clean_environmentprobe") + row = layout.row() + row.prop(sceneProperties, "tlm_environment_probe_engine") + row = layout.row() + row.prop(sceneProperties, "tlm_cmft_path") + row = layout.row() + row.prop(sceneProperties, "tlm_environment_probe_resolution") + row = layout.row() + row.prop(sceneProperties, "tlm_create_spherical") + + if sceneProperties.tlm_create_spherical: + + row = layout.row() + row.prop(sceneProperties, "tlm_invert_direction") + row = layout.row() + row.prop(sceneProperties, "tlm_write_sh") + row = layout.row() + row.prop(sceneProperties, "tlm_write_radiance") + + row = layout.row(align=True) + row.label(text="Load lightmaps") + row = layout.row() + row.prop(sceneProperties, "tlm_load_folder") + row = layout.row() + row.operator("tlm.load_lightmaps") + +class TLM_PT_Additional(bpy.types.Panel): + bl_label = "Additional" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "render" + bl_options = {'DEFAULT_CLOSED'} + bl_parent_id = "ARM_PT_BakePanel" + + @classmethod + def poll(self, context): + scene = context.scene + return scene.arm_bakemode == "Lightmap" + + def draw(self, context): + layout = self.layout + scene = context.scene + sceneProperties = scene.TLM_SceneProperties + atlasListItem = scene.TLM_AtlasListItem + atlasList = scene.TLM_AtlasList + postatlasListItem = scene.TLM_PostAtlasListItem + postatlasList = scene.TLM_PostAtlasList + + layout.label(text="Network Rendering") + row = layout.row() + row.operator("tlm.start_server") + layout.label(text="Atlas Groups") + row = layout.row() + row.prop(sceneProperties, "tlm_atlas_mode", expand=True) + + if sceneProperties.tlm_atlas_mode == "Prepack": + + rows = 2 + if len(atlasList) > 1: + rows = 4 + row = layout.row() + row.template_list("TLM_UL_AtlasList", "Atlas List", scene, "TLM_AtlasList", scene, "TLM_AtlasListItem", rows=rows) + col = row.column(align=True) + col.operator("tlm_atlaslist.new_item", icon='ADD', text="") + col.operator("tlm_atlaslist.delete_item", icon='REMOVE', text="") + + if atlasListItem >= 0 and len(atlasList) > 0: + item = atlasList[atlasListItem] + layout.prop(item, "tlm_atlas_lightmap_unwrap_mode") + layout.prop(item, "tlm_atlas_lightmap_resolution") + layout.prop(item, "tlm_atlas_unwrap_margin") + + amount = 0 + + for obj in bpy.context.scene.objects: + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": + if obj.TLM_ObjectProperties.tlm_atlas_pointer == item.name: + amount = amount + 1 + + layout.label(text="Objects: " + str(amount)) + + else: + + layout.label(text="Postpacking is unstable.") + + cv2 = importlib.util.find_spec("cv2") + + if cv2 is None: + + row = layout.row(align=True) + row.label(text="OpenCV is not installed. Install it through preferences.") + + else: + + rows = 2 + if len(atlasList) > 1: + rows = 4 + row = layout.row() + row.template_list("TLM_UL_PostAtlasList", "PostList", scene, "TLM_PostAtlasList", scene, "TLM_PostAtlasListItem", rows=rows) + col = row.column(align=True) + col.operator("tlm_postatlaslist.new_item", icon='ADD', text="") + col.operator("tlm_postatlaslist.delete_item", icon='REMOVE', text="") + + if postatlasListItem >= 0 and len(postatlasList) > 0: + item = postatlasList[postatlasListItem] + layout.prop(item, "tlm_atlas_lightmap_resolution") + + #Below list object counter + amount = 0 + utilized = 0 + atlasUsedArea = 0 + atlasSize = item.tlm_atlas_lightmap_resolution + + for obj in bpy.context.scene.objects: + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + if obj.TLM_ObjectProperties.tlm_postpack_object: + if obj.TLM_ObjectProperties.tlm_postatlas_pointer == item.name: + amount = amount + 1 + + atlasUsedArea += int(obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution) ** 2 + + row = layout.row() + row.prop(item, "tlm_atlas_repack_on_cleanup") + + #TODO SET A CHECK FOR THIS! ADD A CV2 CHECK TO UTILITY! + cv2 = True + + if cv2: + row = layout.row() + row.prop(item, "tlm_atlas_dilation") + layout.label(text="Objects: " + str(amount)) + + utilized = atlasUsedArea / (int(atlasSize) ** 2) + layout.label(text="Utilized: " + str(utilized * 100) + "%") + + if (utilized * 100) > 100: + layout.label(text="Warning! Overflow not yet supported") \ No newline at end of file diff --git a/blender/arm/lightmapper/panels/world.py b/blender/arm/lightmapper/panels/world.py new file mode 100644 index 00000000..b3c5d294 --- /dev/null +++ b/blender/arm/lightmapper/panels/world.py @@ -0,0 +1,17 @@ +import bpy +from bpy.props import * +from bpy.types import Menu, Panel + +class TLM_PT_WorldMenu(bpy.types.Panel): + bl_label = "The Lightmapper" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "world" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + layout = self.layout + scene = context.scene + obj = bpy.context.object + layout.use_property_split = True + layout.use_property_decorate = False \ No newline at end of file diff --git a/blender/arm/lightmapper/preferences/__init__.py b/blender/arm/lightmapper/preferences/__init__.py new file mode 100644 index 00000000..94cdfaea --- /dev/null +++ b/blender/arm/lightmapper/preferences/__init__.py @@ -0,0 +1,16 @@ +import bpy +from bpy.utils import register_class, unregister_class +from . import addon_preferences +#from . import build, clean, explore, encode, installopencv + +classes = [ + addon_preferences.TLM_AddonPreferences +] + +def register(): + for cls in classes: + register_class(cls) + +def unregister(): + for cls in classes: + unregister_class(cls) \ No newline at end of file diff --git a/blender/arm/lightmapper/preferences/addon_preferences.py b/blender/arm/lightmapper/preferences/addon_preferences.py new file mode 100644 index 00000000..173ac71a --- /dev/null +++ b/blender/arm/lightmapper/preferences/addon_preferences.py @@ -0,0 +1,75 @@ +import bpy, platform +from os.path import basename, dirname +from bpy.types import AddonPreferences +from .. operators import installopencv +import importlib + +class TLM_AddonPreferences(AddonPreferences): + + bl_idname = "thelightmapper" + + def draw(self, context): + + layout = self.layout + + box = layout.box() + row = box.row() + row.label(text="OpenCV") + + cv2 = importlib.util.find_spec("cv2") + + if cv2 is not None: + row.label(text="OpenCV installed") + else: + if platform.system() == "Windows": + row.label(text="OpenCV not found - Install as administrator!", icon_value=2) + else: + row.label(text="OpenCV not found - Click to install!", icon_value=2) + row = box.row() + row.operator("tlm.install_opencv_lightmaps", icon="PREFERENCES") + + box = layout.box() + row = box.row() + row.label(text="Blender Xatlas") + if "blender_xatlas" in bpy.context.preferences.addons.keys(): + row.label(text="Blender Xatlas installed and available") + else: + row.label(text="Blender Xatlas not installed", icon_value=2) + row = box.row() + row.label(text="Github: https://github.com/mattedicksoncom/blender-xatlas") + + box = layout.box() + row = box.row() + row.label(text="RizomUV Bridge") + row.label(text="Coming soon") + + box = layout.box() + row = box.row() + row.label(text="UVPackmaster") + row.label(text="Coming soon") + + texel_density_addon = False + for addon in bpy.context.preferences.addons.keys(): + if addon.startswith("Texel_Density"): + texel_density_addon = True + + box = layout.box() + row = box.row() + row.label(text="Texel Density Checker") + if texel_density_addon: + row.label(text="Texel Density Checker installed and available") + else: + row.label(text="Texel Density Checker", icon_value=2) + row.label(text="Coming soon") + row = box.row() + row.label(text="Github: https://github.com/mrven/Blender-Texel-Density-Checker") + + box = layout.box() + row = box.row() + row.label(text="LuxCoreRender") + row.label(text="Coming soon") + + box = layout.box() + row = box.row() + row.label(text="OctaneRender") + row.label(text="Coming soon") \ No newline at end of file diff --git a/blender/arm/lightmapper/properties/__init__.py b/blender/arm/lightmapper/properties/__init__.py index 052a173b..0a011de9 100644 --- a/blender/arm/lightmapper/properties/__init__.py +++ b/blender/arm/lightmapper/properties/__init__.py @@ -1,7 +1,7 @@ import bpy from bpy.utils import register_class, unregister_class -from . import scene, object, atlas -from . renderer import cycles, luxcorerender +from . import scene, object, atlas, image +from . renderer import cycles, luxcorerender, octanerender from . denoiser import oidn, optix classes = [ @@ -9,12 +9,14 @@ classes = [ object.TLM_ObjectProperties, cycles.TLM_CyclesSceneProperties, luxcorerender.TLM_LuxCoreSceneProperties, + octanerender.TLM_OctanerenderSceneProperties, oidn.TLM_OIDNEngineProperties, optix.TLM_OptixEngineProperties, atlas.TLM_AtlasListItem, atlas.TLM_UL_AtlasList, atlas.TLM_PostAtlasListItem, - atlas.TLM_UL_PostAtlasList + atlas.TLM_UL_PostAtlasList, + image.TLM_ImageProperties ] def register(): @@ -25,12 +27,14 @@ def register(): bpy.types.Object.TLM_ObjectProperties = bpy.props.PointerProperty(type=object.TLM_ObjectProperties) bpy.types.Scene.TLM_EngineProperties = bpy.props.PointerProperty(type=cycles.TLM_CyclesSceneProperties) bpy.types.Scene.TLM_Engine2Properties = bpy.props.PointerProperty(type=luxcorerender.TLM_LuxCoreSceneProperties) + bpy.types.Scene.TLM_Engine3Properties = bpy.props.PointerProperty(type=octanerender.TLM_OctanerenderSceneProperties) bpy.types.Scene.TLM_OIDNEngineProperties = bpy.props.PointerProperty(type=oidn.TLM_OIDNEngineProperties) bpy.types.Scene.TLM_OptixEngineProperties = bpy.props.PointerProperty(type=optix.TLM_OptixEngineProperties) bpy.types.Scene.TLM_AtlasListItem = bpy.props.IntProperty(name="Index for my_list", default=0) bpy.types.Scene.TLM_AtlasList = bpy.props.CollectionProperty(type=atlas.TLM_AtlasListItem) bpy.types.Scene.TLM_PostAtlasListItem = bpy.props.IntProperty(name="Index for my_list", default=0) bpy.types.Scene.TLM_PostAtlasList = bpy.props.CollectionProperty(type=atlas.TLM_PostAtlasListItem) + bpy.types.Image.TLM_ImageProperties = bpy.props.PointerProperty(type=image.TLM_ImageProperties) bpy.types.Material.TLM_ignore = bpy.props.BoolProperty(name="Skip material", description="Ignore material for lightmapped object", default=False) @@ -42,9 +46,11 @@ def unregister(): del bpy.types.Object.TLM_ObjectProperties del bpy.types.Scene.TLM_EngineProperties del bpy.types.Scene.TLM_Engine2Properties + del bpy.types.Scene.TLM_Engine3Properties del bpy.types.Scene.TLM_OIDNEngineProperties del bpy.types.Scene.TLM_OptixEngineProperties del bpy.types.Scene.TLM_AtlasListItem del bpy.types.Scene.TLM_AtlasList del bpy.types.Scene.TLM_PostAtlasListItem - del bpy.types.Scene.TLM_PostAtlasList \ No newline at end of file + del bpy.types.Scene.TLM_PostAtlasList + del bpy.types.Image.TLM_ImageProperties \ No newline at end of file diff --git a/blender/arm/lightmapper/properties/atlas.py b/blender/arm/lightmapper/properties/atlas.py index baccd926..8154f72c 100644 --- a/blender/arm/lightmapper/properties/atlas.py +++ b/blender/arm/lightmapper/properties/atlas.py @@ -34,12 +34,16 @@ class TLM_PostAtlasListItem(bpy.types.PropertyGroup): max=1.0, subtype='FACTOR') - tlm_atlas_lightmap_unwrap_mode : EnumProperty( - items = [('Lightmap', 'Lightmap', 'TODO'), - ('SmartProject', 'Smart Project', 'TODO'), - ('Xatlas', 'Xatlas', 'TODO')], + unwrap_modes = [('Lightmap', 'Lightmap', 'Use Blender Lightmap Pack algorithm'), + ('SmartProject', 'Smart Project', 'Use Blender Smart Project algorithm')] + + if "blender_xatlas" in bpy.context.preferences.addons.keys(): + unwrap_modes.append(('Xatlas', 'Xatlas', 'Use Xatlas addon packing algorithm')) + + tlm_postatlas_lightmap_unwrap_mode : EnumProperty( + items = unwrap_modes, name = "Unwrap Mode", - description="TODO", + description="Atlas unwrapping method", default='SmartProject') class TLM_UL_PostAtlasList(bpy.types.UIList): @@ -51,7 +55,7 @@ class TLM_UL_PostAtlasList(bpy.types.UIList): #In list object counter amount = 0 - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: if obj.TLM_ObjectProperties.tlm_postpack_object: if obj.TLM_ObjectProperties.tlm_postatlas_pointer == item.name: @@ -69,9 +73,6 @@ class TLM_UL_PostAtlasList(bpy.types.UIList): layout.alignment = 'CENTER' layout.label(text="", icon = custom_icon) - - - class TLM_AtlasListItem(bpy.types.PropertyGroup): obj: PointerProperty(type=bpy.types.Object, description="The object to bake") tlm_atlas_lightmap_resolution : EnumProperty( @@ -95,12 +96,17 @@ class TLM_AtlasListItem(bpy.types.PropertyGroup): max=1.0, subtype='FACTOR') + unwrap_modes = [('Lightmap', 'Lightmap', 'Use Blender Lightmap Pack algorithm'), + ('SmartProject', 'Smart Project', 'Use Blender Smart Project algorithm'), + ('Copy', 'Copy existing', 'Use the existing UV channel')] + + if "blender_xatlas" in bpy.context.preferences.addons.keys(): + unwrap_modes.append(('Xatlas', 'Xatlas', 'Use Xatlas addon packing algorithm')) + tlm_atlas_lightmap_unwrap_mode : EnumProperty( - items = [('Lightmap', 'Lightmap', 'TODO'), - ('SmartProject', 'Smart Project', 'TODO'), - ('Xatlas', 'Xatlas', 'TODO')], + items = unwrap_modes, name = "Unwrap Mode", - description="TODO", + description="Atlas unwrapping method", default='SmartProject') class TLM_UL_AtlasList(bpy.types.UIList): @@ -111,7 +117,7 @@ class TLM_UL_AtlasList(bpy.types.UIList): amount = 0 - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": if obj.TLM_ObjectProperties.tlm_atlas_pointer == item.name: diff --git a/blender/arm/lightmapper/properties/image.py b/blender/arm/lightmapper/properties/image.py index e6e75766..a81169c4 100644 --- a/blender/arm/lightmapper/properties/image.py +++ b/blender/arm/lightmapper/properties/image.py @@ -1,10 +1,26 @@ import bpy from bpy.props import * -class TLM_ObjectProperties(bpy.types.PropertyGroup): - tlm_image_scale_method : EnumProperty( - items = [('Native', 'Native', 'TODO'), - ('OpenCV', 'OpenCV', 'TODO')], +class TLM_ImageProperties(bpy.types.PropertyGroup): + tlm_image_scale_engine : EnumProperty( + items = [('OpenCV', 'OpenCV', 'TODO')], name = "Scaling engine", description="TODO", - default='Native') \ No newline at end of file + default='OpenCV') + + #('Native', 'Native', 'TODO'), + + tlm_image_scale_method : EnumProperty( + items = [('Nearest', 'Nearest', 'TODO'), + ('Area', 'Area', 'TODO'), + ('Linear', 'Linear', 'TODO'), + ('Cubic', 'Cubic', 'TODO'), + ('Lanczos', 'Lanczos', 'TODO')], + name = "Scaling method", + description="TODO", + default='Lanczos') + + tlm_image_cache_switch : BoolProperty( + name="Cache for quickswitch", + description="Caches scaled images for quick switching", + default=True) \ No newline at end of file diff --git a/blender/arm/lightmapper/properties/object.py b/blender/arm/lightmapper/properties/object.py index 7738add9..c5c2fe47 100644 --- a/blender/arm/lightmapper/properties/object.py +++ b/blender/arm/lightmapper/properties/object.py @@ -7,7 +7,7 @@ class TLM_ObjectProperties(bpy.types.PropertyGroup): tlm_atlas_pointer : StringProperty( name = "Atlas Group", - description = "Atlas Lightmap Group", + description = "", default = "") tlm_postatlas_pointer : StringProperty( @@ -51,8 +51,7 @@ class TLM_ObjectProperties(bpy.types.PropertyGroup): unwrap_modes = [('Lightmap', 'Lightmap', 'TODO'), ('SmartProject', 'Smart Project', 'TODO'), - ('CopyExisting', 'Copy Existing', 'TODO'), - ('AtlasGroupA', 'Atlas Group (Prepack)', 'TODO')] + ('AtlasGroupA', 'Atlas Group (Prepack)', 'Attaches the object to a prepack Atlas group. Will overwrite UV map on build.')] tlm_postpack_object : BoolProperty( #CHECK INSTEAD OF ATLASGROUPB name="Postpack object", @@ -145,4 +144,14 @@ class TLM_ObjectProperties(bpy.types.PropertyGroup): name="Median kernel", default=3, min=1, - max=5) \ No newline at end of file + max=5) + + tlm_use_default_channel : BoolProperty( + name="Use default UV channel", + description="Will either use or create the default UV Channel 'UVMap_Lightmap' upon build.", + default=True) + + tlm_uv_channel : StringProperty( + name = "UV Channel", + description = "Use any custom UV Channel for the lightmap", + default = "UVMap") \ No newline at end of file diff --git a/blender/arm/lightmapper/properties/renderer/cycles.py b/blender/arm/lightmapper/properties/renderer/cycles.py index ad99a664..d3763400 100644 --- a/blender/arm/lightmapper/properties/renderer/cycles.py +++ b/blender/arm/lightmapper/properties/renderer/cycles.py @@ -21,6 +21,16 @@ class TLM_CyclesSceneProperties(bpy.types.PropertyGroup): description="Select baking quality", default="0") + targets = [('texture', 'Image texture', 'Build to image texture')] + if (2, 92, 0) >= bpy.app.version: + targets.append(('vertex', 'Vertex colors', 'Build to vertex colors')) + + tlm_target : EnumProperty( + items = targets, + name = "Build Target", + description="Select target to build to", + default="texture") + tlm_resolution_scale : EnumProperty( items = [('1', '1/1', '1'), ('2', '1/2', '2'), @@ -45,10 +55,12 @@ class TLM_CyclesSceneProperties(bpy.types.PropertyGroup): description="Select bake mode", default="Foreground") + caching_modes = [('Copy', 'Copy', 'More overhead; allows for network.')] + + #caching_modes.append(('Cache', 'Cache', 'Cache in separate blend'),('Node', 'Node restore', 'EXPERIMENTAL! Use with care')) + tlm_caching_mode : EnumProperty( - items = [('Copy', 'Copy', 'More overhead; allows for network.'), - ('Cache', 'Cache', 'Cache in separate blend'), - ('Node', 'Node restore', 'EXPERIMENTAL! Use with care')], + items = caching_modes, name = "Caching mode", description="Select cache mode", default="Copy") @@ -88,8 +100,16 @@ class TLM_CyclesSceneProperties(bpy.types.PropertyGroup): tlm_lighting_mode : EnumProperty( items = [('combined', 'Combined', 'Bake combined lighting'), + ('combinedao', 'Combined+AO', 'Bake combined lighting with Ambient Occlusion'), ('indirect', 'Indirect', 'Bake indirect lighting'), - ('ao', 'AO', 'Bake only Ambient Occlusion')], +# ('indirectao', 'Indirect+AO', 'Bake indirect lighting with Ambient Occlusion'), + ('ao', 'AO', 'Bake only Ambient Occlusion'), + ('complete', 'Complete', 'Bake complete map')], name = "Lighting mode", description="TODO.", - default="combined") \ No newline at end of file + default="combined") + + tlm_premultiply_ao : BoolProperty( + name="Premultiply AO", + description="Ambient Occlusion will be premultiplied together with lightmaps, requiring less textures.", + default=True) \ No newline at end of file diff --git a/blender/arm/lightmapper/properties/renderer/octanerender.py b/blender/arm/lightmapper/properties/renderer/octanerender.py index e69de29b..8c66cf13 100644 --- a/blender/arm/lightmapper/properties/renderer/octanerender.py +++ b/blender/arm/lightmapper/properties/renderer/octanerender.py @@ -0,0 +1,10 @@ +import bpy +from bpy.props import * + +class TLM_OctanerenderSceneProperties(bpy.types.PropertyGroup): + + tlm_lightmap_savedir : StringProperty( + name="Lightmap Directory", + description="TODO", + default="Lightmaps", + subtype="FILE_PATH") \ No newline at end of file diff --git a/blender/arm/lightmapper/properties/scene.py b/blender/arm/lightmapper/properties/scene.py index 6652b548..36c74fbc 100644 --- a/blender/arm/lightmapper/properties/scene.py +++ b/blender/arm/lightmapper/properties/scene.py @@ -1,11 +1,19 @@ -import bpy +import bpy, os from bpy.props import * +from .. utility import utility + +def transfer_load(): + load_folder = bpy.context.scene.TLM_SceneProperties.tlm_load_folder + lightmap_folder = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir) + print(load_folder) + print(lightmap_folder) + #transfer_assets(True, load_folder, lightmap_folder) class TLM_SceneProperties(bpy.types.PropertyGroup): engines = [('Cycles', 'Cycles', 'Use Cycles for lightmapping')] - engines.append(('LuxCoreRender', 'LuxCoreRender', 'Use LuxCoreRender for lightmapping')) + #engines.append(('LuxCoreRender', 'LuxCoreRender', 'Use LuxCoreRender for lightmapping')) #engines.append(('OctaneRender', 'Octane Render', 'Use Octane Render for lightmapping')) tlm_atlas_pointer : StringProperty( @@ -112,7 +120,7 @@ class TLM_SceneProperties(bpy.types.PropertyGroup): #FILTERING SETTINGS GROUP tlm_filtering_use : BoolProperty( - name="Enable Filtering", + name="Enable denoising", description="Enable denoising for lightmaps", default=False) @@ -182,6 +190,17 @@ class TLM_SceneProperties(bpy.types.PropertyGroup): min=1, max=5) + tlm_clamp_hdr : BoolProperty( + name="Enable HDR Clamp", + description="Clamp HDR Value", + default=False) + + tlm_clamp_hdr_value : IntProperty( + name="HDR Clamp value", + default=10, + min=0, + max=20) + #Encoding properties tlm_encoding_use : BoolProperty( name="Enable encoding", @@ -197,12 +216,13 @@ class TLM_SceneProperties(bpy.types.PropertyGroup): encoding_modes_1 = [('RGBM', 'RGBM', '8-bit HDR encoding. Good for compatibility, good for memory but has banding issues.'), ('RGBD', 'RGBD', '8-bit HDR encoding. Similar to RGBM.'), - ('HDR', 'HDR', '32-bit HDR encoding. Best quality, but high memory usage and not compatible with all devices.')] + ('HDR', 'HDR', '32-bit HDR encoding. Best quality, but high memory usage and not compatible with all devices.'), + ('SDR', 'SDR', '8-bit flat encoding.')] - encoding_modes_2 = [('RGBM', 'RGBM', '8-bit HDR encoding. Good for compatibility, good for memory but has banding issues.'), - ('RGBD', 'RGBD', '8-bit HDR encoding. Similar to RGBM.'), + encoding_modes_2 = [('RGBD', 'RGBD', '8-bit HDR encoding. Similar to RGBM.'), ('LogLuv', 'LogLuv', '8-bit HDR encoding. Different.'), - ('HDR', 'HDR', '32-bit HDR encoding. Best quality, but high memory usage and not compatible with all devices.')] + ('HDR', 'HDR', '32-bit HDR encoding. Best quality, but high memory usage and not compatible with all devices.'), + ('SDR', 'SDR', '8-bit flat encoding.')] tlm_encoding_mode_a : EnumProperty( items = encoding_modes_1, @@ -275,8 +295,7 @@ class TLM_SceneProperties(bpy.types.PropertyGroup): tlm_mesh_lightmap_unwrap_mode : EnumProperty( items = [('Lightmap', 'Lightmap', 'TODO'), ('SmartProject', 'Smart Project', 'TODO'), - ('CopyExisting', 'Copy Existing', 'TODO'), - ('AtlasGroupA', 'Atlas Group (Prepack)', 'TODO'), + ('AtlasGroupA', 'Atlas Group (Prepack)', 'Attaches the object to a prepack Atlas group. Will overwrite UV map on build.'), ('Xatlas', 'Xatlas', 'TODO')], name = "Unwrap Mode", description="TODO", @@ -317,12 +336,30 @@ class TLM_SceneProperties(bpy.types.PropertyGroup): tlm_metallic_clamp : EnumProperty( items = [('ignore', 'Ignore', 'Ignore clamping'), + ('skip', 'Skip', 'Skip baking metallic materials'), ('zero', 'Zero', 'Set zero'), ('limit', 'Limit', 'Clamp to 0.9')], name = "Metallic clamping", description="TODO.", default="ignore") + tlm_texture_interpolation : EnumProperty( + items = [('Smart', 'Smart', 'Bicubic when magnifying.'), + ('Cubic', 'Cubic', 'Cubic interpolation'), + ('Closest', 'Closest', 'No interpolation'), + ('Linear', 'Linear', 'Linear')], + name = "Texture interpolation", + description="Texture interpolation.", + default="Linear") + + tlm_texture_extrapolation : EnumProperty( + items = [('REPEAT', 'Repeat', 'Repeat in both direction.'), + ('EXTEND', 'Extend', 'Extend by repeating edge pixels.'), + ('CLIP', 'Clip', 'Clip to image size')], + name = "Texture extrapolation", + description="Texture extrapolation.", + default="EXTEND") + tlm_verbose : BoolProperty( name="Verbose", description="Verbose console output", @@ -409,4 +446,52 @@ class TLM_SceneProperties(bpy.types.PropertyGroup): ('CYCLES', 'Cycles', 'TODO')], name = "Probe Render Engine", description="TODO", - default='BLENDER_EEVEE') \ No newline at end of file + default='BLENDER_EEVEE') + + tlm_load_folder : StringProperty( + name="Load Folder", + description="Load existing lightmaps from folder", + subtype="DIR_PATH") + + tlm_utility_set : EnumProperty( + items = [('Scene', 'Scene', 'Set for all objects in the scene.'), + ('Selection', 'Selection', 'Set for selected objects.'), + ('Enabled', 'Enabled', 'Set for objects that has been enabled for lightmapping.')], + name = "Set", + description="Utility selection set", + default='Scene') + + tlm_resolution_weight : EnumProperty( + items = [('Single', 'Single', 'Set a single resolution for all objects.'), + ('Dimension', 'Dimension', 'Distribute resolutions based on object dimensions.'), + ('Surface', 'Surface', 'Distribute resolutions based on mesh surface area.'), + ('Volume', 'Volume', 'Distribute resolutions based on mesh volume.')], + name = "Resolution weight", + description="Method for setting resolution value", + default='Single') + #Todo add vertex color option + + tlm_resolution_min : EnumProperty( + items = [('32', '32', 'TODO'), + ('64', '64', 'TODO'), + ('128', '128', 'TODO'), + ('256', '256', 'TODO'), + ('512', '512', 'TODO'), + ('1024', '1024', 'TODO'), + ('2048', '2048', 'TODO'), + ('4096', '4096', 'TODO')], + name = "Minimum resolution", + description="Minimum distributed resolution", + default='32') + + tlm_resolution_max : EnumProperty( + items = [('64', '64', 'TODO'), + ('128', '128', 'TODO'), + ('256', '256', 'TODO'), + ('512', '512', 'TODO'), + ('1024', '1024', 'TODO'), + ('2048', '2048', 'TODO'), + ('4096', '4096', 'TODO')], + name = "Maximum resolution", + description="Maximum distributed resolution", + default='256') \ No newline at end of file diff --git a/blender/arm/lightmapper/utility/build.py b/blender/arm/lightmapper/utility/build.py index a8ad80dd..891ebcf4 100644 --- a/blender/arm/lightmapper/utility/build.py +++ b/blender/arm/lightmapper/utility/build.py @@ -1,17 +1,20 @@ import bpy, os, subprocess, sys, platform, aud, json, datetime, socket -import threading + from . import encoding, pack from . cycles import lightmap, prepare, nodes, cache +from . luxcore import setup +from . octane import configure, lightmap2 from . denoiser import integrated, oidn, optix from . filtering import opencv +from . gui import Viewport from .. network import client + from os import listdir from os.path import isfile, join from time import time, sleep from importlib import util previous_settings = {} - postprocess_shutdown = False def prepare_build(self=0, background_mode=False, shutdown_after_build=False): @@ -21,15 +24,31 @@ def prepare_build(self=0, background_mode=False, shutdown_after_build=False): print("Building lightmaps") + if bpy.context.scene.TLM_EngineProperties.tlm_lighting_mode == "combinedao": + + scene = bpy.context.scene + + if not "tlm_plus_mode" in bpy.app.driver_namespace or bpy.app.driver_namespace["tlm_plus_mode"] == 0: + filepath = bpy.data.filepath + dirpath = os.path.join(os.path.dirname(bpy.data.filepath), scene.TLM_EngineProperties.tlm_lightmap_savedir) + if os.path.isdir(dirpath): + for file in os.listdir(dirpath): + os.remove(os.path.join(dirpath + "/" + file)) + bpy.app.driver_namespace["tlm_plus_mode"] = 1 + print("Plus Mode") + if bpy.context.scene.TLM_EngineProperties.tlm_bake_mode == "Foreground" or background_mode==True: global start_time start_time = time() + bpy.app.driver_namespace["tlm_start_time"] = time() scene = bpy.context.scene sceneProperties = scene.TLM_SceneProperties - #Timer start here bound to global + if not background_mode and bpy.context.scene.TLM_EngineProperties.tlm_lighting_mode != "combinedao": + #pass + setGui(1) if check_save(): print("Please save your file first") @@ -52,12 +71,6 @@ def prepare_build(self=0, background_mode=False, shutdown_after_build=False): self.report({'INFO'}, "Error:Filtering - OpenCV not installed") return{'FINISHED'} - #TODO DO some resolution change - #if checkAtlasSize(): - # print("Error: AtlasGroup overflow") - # self.report({'INFO'}, "Error: AtlasGroup overflow - Too many objects") - # return{'FINISHED'} - setMode() dirpath = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir) @@ -67,19 +80,6 @@ def prepare_build(self=0, background_mode=False, shutdown_after_build=False): #Naming check naming_check() - # if sceneProperties.tlm_reset_uv or sceneProperties.tlm_atlas_mode == "Postpack": - # for obj in bpy.data.objects: - # if obj.type == "MESH": - # uv_layers = obj.data.uv_layers - - - - #for uvlayer in uv_layers: - # if uvlayer.name == "UVMap_Lightmap": - # uv_layers.remove(uvlayer) - - ## RENDER DEPENDENCY FROM HERE - if sceneProperties.tlm_lightmap_engine == "Cycles": prepare.init(self, previous_settings) @@ -90,18 +90,14 @@ def prepare_build(self=0, background_mode=False, shutdown_after_build=False): if sceneProperties.tlm_lightmap_engine == "OctaneRender": - pass - - #Renderer - Store settings - - #Renderer - Set settings - - #Renderer - Config objects, lights, world + configure.init(self, previous_settings) begin_build() else: + print("Baking in background") + filepath = bpy.data.filepath bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath) @@ -111,22 +107,7 @@ def prepare_build(self=0, background_mode=False, shutdown_after_build=False): scene = bpy.context.scene sceneProperties = scene.TLM_SceneProperties - #We dynamically load the renderer and denoiser, instead of loading something we don't use - - if sceneProperties.tlm_lightmap_engine == "Cycles": - - pass - - if sceneProperties.tlm_lightmap_engine == "LuxCoreRender": - - pass - - if sceneProperties.tlm_lightmap_engine == "OctaneRender": - - pass - #Timer start here bound to global - if check_save(): print("Please save your file first") self.report({'INFO'}, "Please save your file first") @@ -168,23 +149,12 @@ def prepare_build(self=0, background_mode=False, shutdown_after_build=False): client.connect_client(HOST, PORT, bpy.data.filepath, 0) - # with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - # s.connect((HOST, PORT)) - # message = { - # "call" : 1, - # "command" : 1, - # "enquiry" : 0, - # "args" : bpy.data.filepath - # } - - # s.sendall(json.dumps(message).encode()) - # data = s.recv(1024) - # print(data.decode()) - finish_assemble() else: + print("Background driver process") + bpy.app.driver_namespace["alpha"] = 0 bpy.app.driver_namespace["tlm_process"] = False @@ -196,6 +166,8 @@ def prepare_build(self=0, background_mode=False, shutdown_after_build=False): def distribute_building(): + print("Distributing lightmap building") + #CHECK IF THERE'S AN EXISTING SUBPROCESS if not os.path.isfile(os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir, "process.tlm")): @@ -215,8 +187,16 @@ def distribute_building(): with open(os.path.join(write_directory, "process.tlm"), 'w') as file: json.dump(process_status, file, indent=2) - bpy.app.driver_namespace["tlm_process"] = subprocess.Popen([sys.executable,"-b", blendPath,"--python-expr",'import bpy; import thelightmapper; thelightmapper.addon.utility.build.prepare_build(0, True);'], shell=False, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) - + if (2, 91, 0) > bpy.app.version: + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + bpy.app.driver_namespace["tlm_process"] = subprocess.Popen([sys.executable,"-b", blendPath,"--python-expr",'import bpy; import thelightmapper; thelightmapper.addon.utility.build.prepare_build(0, True);'], shell=False, stdout=subprocess.PIPE) + else: + bpy.app.driver_namespace["tlm_process"] = subprocess.Popen([sys.executable,"-b", blendPath,"--python-expr",'import bpy; import thelightmapper; thelightmapper.addon.utility.build.prepare_build(0, True);'], shell=False, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) + else: + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + bpy.app.driver_namespace["tlm_process"] = subprocess.Popen([bpy.app.binary_path,"-b", blendPath,"--python-expr",'import bpy; import thelightmapper; thelightmapper.addon.utility.build.prepare_build(0, True);'], shell=False, stdout=subprocess.PIPE) + else: + bpy.app.driver_namespace["tlm_process"] = subprocess.Popen([bpy.app.binary_path,"-b", blendPath,"--python-expr",'import bpy; import thelightmapper; thelightmapper.addon.utility.build.prepare_build(0, True);'], shell=False, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) if bpy.context.scene.TLM_SceneProperties.tlm_verbose: print("Started process: " + str(bpy.app.driver_namespace["tlm_process"]) + " at " + str(datetime.datetime.now())) @@ -269,6 +249,10 @@ def finish_assemble(self=0): if sceneProperties.tlm_lightmap_engine == "OctaneRender": pass + if not 'start_time' in globals(): + global start_time + start_time = time() + manage_build(True) def begin_build(): @@ -288,7 +272,8 @@ def begin_build(): pass if sceneProperties.tlm_lightmap_engine == "OctaneRender": - pass + + lightmap2.bake() #Denoiser if sceneProperties.tlm_denoise_use: @@ -429,7 +414,7 @@ def begin_build(): print("Encoding:" + str(file)) encoding.encodeImageRGBMCPU(img, sceneProperties.tlm_encoding_range, dirpath, 0) - if sceneProperties.tlm_encoding_mode_b == "RGBD": + if sceneProperties.tlm_encoding_mode_a == "RGBD": if bpy.context.scene.TLM_SceneProperties.tlm_verbose: print("ENCODING RGBD") @@ -455,6 +440,36 @@ def begin_build(): print("Encoding:" + str(file)) encoding.encodeImageRGBDCPU(img, sceneProperties.tlm_encoding_range, dirpath, 0) + if sceneProperties.tlm_encoding_mode_a == "SDR": + + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("EXR Format") + + ren = bpy.context.scene.render + ren.image_settings.file_format = "PNG" + #ren.image_settings.exr_codec = "scene.TLM_SceneProperties.tlm_exr_codec" + + end = "_baked" + + baked_image_array = [] + + if sceneProperties.tlm_denoise_use: + + end = "_denoised" + + if sceneProperties.tlm_filtering_use: + + end = "_filtered" + + #For each image in folder ending in denoised/filtered + dirfiles = [f for f in listdir(dirpath) if isfile(join(dirpath, f))] + + for file in dirfiles: + if file.endswith(end + ".hdr"): + + img = bpy.data.images.load(os.path.join(dirpath,file)) + img.save_render(img.filepath_raw[:-4] + ".png") + else: if sceneProperties.tlm_encoding_mode_b == "HDR": @@ -562,6 +577,33 @@ def begin_build(): print("Encoding:" + str(file)) encoding.encodeImageRGBDGPU(img, sceneProperties.tlm_encoding_range, dirpath, 0) + if sceneProperties.tlm_encoding_mode_b == "PNG": + + ren = bpy.context.scene.render + ren.image_settings.file_format = "PNG" + #ren.image_settings.exr_codec = "scene.TLM_SceneProperties.tlm_exr_codec" + + end = "_baked" + + baked_image_array = [] + + if sceneProperties.tlm_denoise_use: + + end = "_denoised" + + if sceneProperties.tlm_filtering_use: + + end = "_filtered" + + #For each image in folder ending in denoised/filtered + dirfiles = [f for f in listdir(dirpath) if isfile(join(dirpath, f))] + + for file in dirfiles: + if file.endswith(end + ".hdr"): + + img = bpy.data.images.load(os.path.join(dirpath,file)) + img.save_render(img.filepath_raw[:-4] + ".png") + manage_build() def manage_build(background_pass=False): @@ -610,6 +652,10 @@ def manage_build(background_pass=False): formatEnc = "_encoded.png" + if sceneProperties.tlm_encoding_mode_a == "SDR": + + formatEnc = ".png" + else: print("GPU Encoding") @@ -632,6 +678,10 @@ def manage_build(background_pass=False): formatEnc = "_encoded.png" + if sceneProperties.tlm_encoding_mode_b == "SDR": + + formatEnc = ".png" + if not background_pass: nodes.exchangeLightmapsToPostfix("_baked", end, formatEnc) @@ -653,13 +703,13 @@ def manage_build(background_pass=False): filepath = bpy.data.filepath dirpath = os.path.join(os.path.dirname(bpy.data.filepath), scene.TLM_EngineProperties.tlm_lightmap_savedir) - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: cache.backup_material_restore(obj) - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: cache.backup_material_rename(obj) @@ -672,77 +722,181 @@ def manage_build(background_pass=False): if "_Original" in mat.name: bpy.data.materials.remove(mat) - for obj in bpy.data.objects: - - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: - img_name = obj.name + '_baked' - Lightmapimage = bpy.data.images[img_name] - obj["Lightmap"] = Lightmapimage.filepath_raw + + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": + atlasName = obj.TLM_ObjectProperties.tlm_atlas_pointer + img_name = atlasName + '_baked' + Lightmapimage = bpy.data.images[img_name] + obj["Lightmap"] = Lightmapimage.filepath_raw + elif obj.TLM_ObjectProperties.tlm_postpack_object: + atlasName = obj.TLM_ObjectProperties.tlm_postatlas_pointer + img_name = atlasName + '_baked' + ".hdr" + Lightmapimage = bpy.data.images[img_name] + obj["Lightmap"] = Lightmapimage.filepath_raw + else: + img_name = obj.name + '_baked' + Lightmapimage = bpy.data.images[img_name] + obj["Lightmap"] = Lightmapimage.filepath_raw for image in bpy.data.images: if image.name.endswith("_baked"): bpy.data.images.remove(image, do_unlink=True) - total_time = sec_to_hours((time() - start_time)) - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print(total_time) + if "tlm_plus_mode" in bpy.app.driver_namespace: #First DIR pass - bpy.context.scene["TLM_Buildstat"] = total_time + if bpy.app.driver_namespace["tlm_plus_mode"] == 1: #First DIR pass - reset_settings(previous_settings["settings"]) + filepath = bpy.data.filepath + dirpath = os.path.join(os.path.dirname(bpy.data.filepath), scene.TLM_EngineProperties.tlm_lightmap_savedir) - if sceneProperties.tlm_lightmap_engine == "LuxCoreRender": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + cache.backup_material_restore(obj) - pass + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + cache.backup_material_rename(obj) - if sceneProperties.tlm_lightmap_engine == "OctaneRender": + for mat in bpy.data.materials: + if mat.users < 1: + bpy.data.materials.remove(mat) - pass + for mat in bpy.data.materials: + if mat.name.startswith("."): + if "_Original" in mat.name: + bpy.data.materials.remove(mat) - if bpy.context.scene.TLM_EngineProperties.tlm_bake_mode == "Background": - pass + for image in bpy.data.images: + if image.name.endswith("_baked"): + bpy.data.images.remove(image, do_unlink=True) - if scene.TLM_SceneProperties.tlm_alert_on_finish: + dirpath = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir) - alertSelect = scene.TLM_SceneProperties.tlm_alert_sound + files = os.listdir(dirpath) + + for index, file in enumerate(files): + + filename = extension = os.path.splitext(file)[0] + extension = os.path.splitext(file)[1] + + os.rename(os.path.join(dirpath, file), os.path.join(dirpath, filename + "_dir" + extension)) + + print("First DIR pass complete") + + bpy.app.driver_namespace["tlm_plus_mode"] = 2 + + prepare_build(self=0, background_mode=False, shutdown_after_build=False) + + if not background_pass and bpy.context.scene.TLM_EngineProperties.tlm_lighting_mode != "combinedao": + #pass + setGui(0) + + elif bpy.app.driver_namespace["tlm_plus_mode"] == 2: + + filepath = bpy.data.filepath + + dirpath = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir) + + files = os.listdir(dirpath) + + for index, file in enumerate(files): + + filename = os.path.splitext(file)[0] + extension = os.path.splitext(file)[1] + + if not filename.endswith("_dir"): + os.rename(os.path.join(dirpath, file), os.path.join(dirpath, filename + "_ao" + extension)) + + print("Second AO pass complete") + + total_time = sec_to_hours((time() - start_time)) + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print(total_time) + + bpy.context.scene["TLM_Buildstat"] = total_time + + reset_settings(previous_settings["settings"]) + + bpy.app.driver_namespace["tlm_plus_mode"] = 0 + + if not background_pass: + + #TODO CHANGE! + + nodes.exchangeLightmapsToPostfix(end, end + "_dir", formatEnc) + + nodes.applyAOPass() - if alertSelect == "dash": - soundfile = "dash.ogg" - elif alertSelect == "pingping": - soundfile = "pingping.ogg" - elif alertSelect == "gentle": - soundfile = "gentle.ogg" else: - soundfile = "noot.ogg" - scriptDir = os.path.dirname(os.path.realpath(__file__)) - sound_path = os.path.abspath(os.path.join(scriptDir, '..', 'assets/'+soundfile)) + total_time = sec_to_hours((time() - start_time)) + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print(total_time) - device = aud.Device() - sound = aud.Sound.file(sound_path) - device.play(sound) + bpy.context.scene["TLM_Buildstat"] = total_time - print("Lightmap building finished") + reset_settings(previous_settings["settings"]) - if bpy.app.background: + print("Lightmap building finished") - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Writing background process report") - - write_directory = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir) + if sceneProperties.tlm_lightmap_engine == "LuxCoreRender": - if os.path.exists(os.path.join(write_directory, "process.tlm")): + pass - process_status = json.loads(open(os.path.join(write_directory, "process.tlm")).read()) + if sceneProperties.tlm_lightmap_engine == "OctaneRender": - process_status[1]["completed"] = True + pass - with open(os.path.join(write_directory, "process.tlm"), 'w') as file: - json.dump(process_status, file, indent=2) + if bpy.context.scene.TLM_EngineProperties.tlm_bake_mode == "Background": + pass - if postprocess_shutdown: - sys.exit() + if not background_pass and bpy.context.scene.TLM_EngineProperties.tlm_lighting_mode != "combinedao": + #pass + setGui(0) + + if scene.TLM_SceneProperties.tlm_alert_on_finish: + + alertSelect = scene.TLM_SceneProperties.tlm_alert_sound + + if alertSelect == "dash": + soundfile = "dash.ogg" + elif alertSelect == "pingping": + soundfile = "pingping.ogg" + elif alertSelect == "gentle": + soundfile = "gentle.ogg" + else: + soundfile = "noot.ogg" + + scriptDir = os.path.dirname(os.path.realpath(__file__)) + sound_path = os.path.abspath(os.path.join(scriptDir, '..', 'assets/'+soundfile)) + + device = aud.Device() + sound = aud.Sound.file(sound_path) + device.play(sound) + + if bpy.app.background: + + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Writing background process report") + + write_directory = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir) + + if os.path.exists(os.path.join(write_directory, "process.tlm")): + + process_status = json.loads(open(os.path.join(write_directory, "process.tlm")).read()) + + process_status[1]["completed"] = True + + with open(os.path.join(write_directory, "process.tlm"), 'w') as file: + json.dump(process_status, file, indent=2) + + if postprocess_shutdown: + sys.exit() #TODO - SET BELOW TO UTILITY @@ -770,9 +924,8 @@ def reset_settings(prev_settings): def naming_check(): - for obj in bpy.data.objects: - - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: @@ -829,6 +982,9 @@ def check_save(): def check_denoiser(): + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Checking denoiser path") + scene = bpy.context.scene if scene.TLM_SceneProperties.tlm_denoise_use: @@ -847,8 +1003,8 @@ def check_denoiser(): return 0 def check_materials(): - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: for slot in obj.material_slots: mat = slot.material @@ -868,15 +1024,39 @@ def check_materials(): def sec_to_hours(seconds): a=str(seconds//3600) b=str((seconds%3600)//60) - c=str((seconds%3600)%60) + c=str(round((seconds%3600)%60,1)) d=["{} hours {} mins {} seconds".format(a, b, c)] return d def setMode(): + + obj = bpy.context.scene.objects[0] + bpy.context.view_layer.objects.active = obj + obj.select_set(True) + bpy.ops.object.mode_set(mode='OBJECT') #TODO Make some checks that returns to previous selection +def setGui(mode): + + if mode == 0: + + context = bpy.context + driver = bpy.app.driver_namespace + + if "TLM_UI" in driver: + driver["TLM_UI"].remove_handle() + + if mode == 1: + + #bpy.context.area.tag_redraw() + context = bpy.context + driver = bpy.app.driver_namespace + driver["TLM_UI"] = Viewport.ViewportDraw(context, "Building Lightmaps") + + bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) + def checkAtlasSize(): overflow = False @@ -897,7 +1077,7 @@ def checkAtlasSize(): utilized = 0 atlasUsedArea = 0 - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: if obj.TLM_ObjectProperties.tlm_postpack_object: if obj.TLM_ObjectProperties.tlm_postatlas_pointer == atlas.name: @@ -912,4 +1092,5 @@ def checkAtlasSize(): if overflow == True: return True else: - return False \ No newline at end of file + return False + diff --git a/blender/arm/lightmapper/utility/cycles/cache.py b/blender/arm/lightmapper/utility/cycles/cache.py index 50542c6e..07f068b7 100644 --- a/blender/arm/lightmapper/utility/cycles/cache.py +++ b/blender/arm/lightmapper/utility/cycles/cache.py @@ -2,7 +2,6 @@ import bpy #Todo - Check if already exists, in case multiple objects has the same material - def backup_material_copy(slot): material = slot.material dup = material.copy() @@ -16,25 +15,49 @@ def backup_material_cache_restore(slot, path): if bpy.context.scene.TLM_SceneProperties.tlm_verbose: print("Restore cache") -def backup_material_rename(obj): - if "TLM_PrevMatArray" in obj: - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Renaming material for: " + obj.name) +# def backup_material_restore(obj): #?? +# if bpy.context.scene.TLM_SceneProperties.tlm_verbose: +# print("Restoring material for: " + obj.name) - for slot in obj.material_slots: +#Check if object has TLM_PrevMatArray +# if yes +# - check if array.len is bigger than 0: +# if yes: +# for slot in object: +# originalMaterial = TLM_PrevMatArray[index] +# +# +# if no: +# - In which cases are these? - if slot.material is not None: - if slot.material.name.endswith("_Original"): - newname = slot.material.name[1:-9] - if newname in bpy.data.materials: - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Removing material: " + bpy.data.materials[newname].name) - bpy.data.materials.remove(bpy.data.materials[newname]) - slot.material.name = newname +# if no: +# - In which cases are there not? +# - If a lightmapped material was applied to a non-lightmap object? - del obj["TLM_PrevMatArray"] -def backup_material_restore(obj): + # if bpy.data.materials[originalMaterial].users > 0: #TODO - Check if all lightmapped + + # print("Material has multiple users") + + # if originalMaterial in bpy.data.materials: + # slot.material = bpy.data.materials[originalMaterial] + # slot.material.use_fake_user = False + # elif "." + originalMaterial + "_Original" in bpy.data.materials: + # slot.material = bpy.data.materials["." + originalMaterial + "_Original"] + # slot.material.use_fake_user = False + + # else: + + # print("Material has one user") + + # if "." + originalMaterial + "_Original" in bpy.data.materials: + # slot.material = bpy.data.materials["." + originalMaterial + "_Original"] + # slot.material.use_fake_user = False + # elif originalMaterial in bpy.data.materials: + # slot.material = bpy.data.materials[originalMaterial] + # slot.material.use_fake_user = False + +def backup_material_restore(obj): #?? if bpy.context.scene.TLM_SceneProperties.tlm_verbose: print("Restoring material for: " + obj.name) @@ -59,9 +82,43 @@ def backup_material_restore(obj): originalMaterial = "" if slot.material is not None: - #slot.material.user_clear() Seems to be bad; See: https://developer.blender.org/T49837 - bpy.data.materials.remove(slot.material) + #if slot.material.users < 2: + #slot.material.user_clear() #Seems to be bad; See: https://developer.blender.org/T49837 + #bpy.data.materials.remove(slot.material) if "." + originalMaterial + "_Original" in bpy.data.materials: slot.material = bpy.data.materials["." + originalMaterial + "_Original"] - slot.material.use_fake_user = False \ No newline at end of file + slot.material.use_fake_user = False + + else: + + print("No previous material for " + obj.name) + + else: + + print("No previous material for " + obj.name) + +def backup_material_rename(obj): #?? + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Renaming material for: " + obj.name) + + + if "TLM_PrevMatArray" in obj: + + for slot in obj.material_slots: + + if slot.material is not None: + if slot.material.name.endswith("_Original"): + newname = slot.material.name[1:-9] + if newname in bpy.data.materials: + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Removing material: " + bpy.data.materials[newname].name) + #if bpy.data.materials[newname].users < 2: + #bpy.data.materials.remove(bpy.data.materials[newname]) #TODO - Maybe remove this + slot.material.name = newname + + del obj["TLM_PrevMatArray"] + + else: + + print("No Previous material array for: " + obj.name) \ No newline at end of file diff --git a/blender/arm/lightmapper/utility/cycles/lightmap.py b/blender/arm/lightmapper/utility/cycles/lightmap.py index 8588bee8..0d0c56e1 100644 --- a/blender/arm/lightmapper/utility/cycles/lightmap.py +++ b/blender/arm/lightmapper/utility/cycles/lightmap.py @@ -1,25 +1,26 @@ import bpy, os +from .. import build +from time import time, sleep -def bake(): +def bake(plus_pass=0): - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: bpy.ops.object.select_all(action='DESELECT') obj.select_set(False) iterNum = 0 currentIterNum = 0 - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: iterNum = iterNum + 1 if iterNum > 1: iterNum = iterNum - 1 - for obj in bpy.data.objects: - if obj.type == 'MESH': - + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: scene = bpy.context.scene @@ -32,19 +33,45 @@ def bake(): obj.hide_render = False scene.render.bake.use_clear = False - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Baking " + str(currentIterNum) + "/" + str(iterNum) + " (" + str(round(currentIterNum/iterNum*100, 2)) + "%) : " + obj.name) + #os.system("cls") - if scene.TLM_EngineProperties.tlm_lighting_mode == "combined" or scene.TLM_EngineProperties.tlm_lighting_mode == "combinedAO": + #if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Baking " + str(currentIterNum) + "/" + str(iterNum) + " (" + str(round(currentIterNum/iterNum*100, 2)) + "%) : " + obj.name) + #elapsed = build.sec_to_hours((time() - bpy.app.driver_namespace["tlm_start_time"])) + #print("Baked: " + str(currentIterNum) + " | Left: " + str(iterNum-currentIterNum)) + elapsedSeconds = time() - bpy.app.driver_namespace["tlm_start_time"] + bakedObjects = currentIterNum + bakedLeft = iterNum-currentIterNum + if bakedObjects == 0: + bakedObjects = 1 + averagePrBake = elapsedSeconds / bakedObjects + remaining = averagePrBake * bakedLeft + #print(time() - bpy.app.driver_namespace["tlm_start_time"]) + print("Elapsed time: " + str(round(elapsedSeconds, 2)) + "s | ETA remaining: " + str(round(remaining, 2)) + "s") #str(elapsed[0]) + #print("Averaged: " + str(averagePrBake)) + #print("Remaining: " + str(remaining)) + + if scene.TLM_EngineProperties.tlm_target == "vertex": + scene.render.bake_target = "VERTEX_COLORS" + + if scene.TLM_EngineProperties.tlm_lighting_mode == "combined": bpy.ops.object.bake(type="DIFFUSE", pass_filter={"DIRECT","INDIRECT"}, margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False) - elif scene.TLM_EngineProperties.tlm_lighting_mode == "indirect" or scene.TLM_EngineProperties.tlm_lighting_mode == "indirectAO": + elif scene.TLM_EngineProperties.tlm_lighting_mode == "indirect": bpy.ops.object.bake(type="DIFFUSE", pass_filter={"INDIRECT"}, margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False) elif scene.TLM_EngineProperties.tlm_lighting_mode == "ao": bpy.ops.object.bake(type="AO", margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False) + elif scene.TLM_EngineProperties.tlm_lighting_mode == "combinedao": + + if bpy.app.driver_namespace["tlm_plus_mode"] == 1: + bpy.ops.object.bake(type="DIFFUSE", pass_filter={"DIRECT","INDIRECT"}, margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False) + elif bpy.app.driver_namespace["tlm_plus_mode"] == 2: + bpy.ops.object.bake(type="AO", margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False) + elif scene.TLM_EngineProperties.tlm_lighting_mode == "complete": - bpy.ops.object.bake(type="COMBINED", margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False) + bpy.ops.object.bake(type="COMBINED", margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False) else: bpy.ops.object.bake(type="DIFFUSE", pass_filter={"DIRECT","INDIRECT"}, margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False) + bpy.ops.object.select_all(action='DESELECT') currentIterNum = currentIterNum + 1 diff --git a/blender/arm/lightmapper/utility/cycles/nodes.py b/blender/arm/lightmapper/utility/cycles/nodes.py index bb42a895..9eb24d88 100644 --- a/blender/arm/lightmapper/utility/cycles/nodes.py +++ b/blender/arm/lightmapper/utility/cycles/nodes.py @@ -1,8 +1,8 @@ import bpy, os def apply_lightmaps(): - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: for slot in obj.material_slots: mat = slot.material @@ -32,8 +32,12 @@ def apply_lightmaps(): def apply_materials(): - for obj in bpy.data.objects: - if obj.type == "MESH": + + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Applying materials") + + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: uv_layers = obj.data.uv_layers @@ -90,7 +94,7 @@ def apply_materials(): for node in nodes: if node.name == "Baked Image": lightmapNode = node - lightmapNode.location = -800, 300 + lightmapNode.location = -1200, 300 lightmapNode.name = "TLM_Lightmap" foundBakedNode = True @@ -98,9 +102,10 @@ def apply_materials(): if not foundBakedNode: lightmapNode = node_tree.nodes.new(type="ShaderNodeTexImage") - lightmapNode.location = -300, 300 + lightmapNode.location = -1200, 300 lightmapNode.name = "TLM_Lightmap" - lightmapNode.interpolation = "Smart" + lightmapNode.interpolation = bpy.context.scene.TLM_SceneProperties.tlm_texture_interpolation + lightmapNode.extension = bpy.context.scene.TLM_SceneProperties.tlm_texture_extrapolation if (obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA" and obj.TLM_ObjectProperties.tlm_atlas_pointer != ""): lightmapNode.image = bpy.data.images[obj.TLM_ObjectProperties.tlm_atlas_pointer + "_baked"] @@ -118,37 +123,32 @@ def apply_materials(): #Find mainnode mainNode = outputNode.inputs[0].links[0].from_node - #Clamp metallic - - # if scene.TLM_SceneProperties.tlm_metallic_clamp != "ignore": - # if mainNode.type == "BSDF_PRINCIPLED": - - # if len(mainNode.inputs[4].links) == 0: - - # if scene.TLM_SceneProperties.tlm_metallic_clamp == "zero": - # mainNode.inputs[4].default_value = 0.0 - # else: - # mainNode.inputs[4].default_value = 0.99 - - # else: - - # pass - #Add all nodes first #Add lightmap multipliction texture mixNode = node_tree.nodes.new(type="ShaderNodeMixRGB") mixNode.name = "Lightmap_Multiplication" - mixNode.location = -300, 300 + mixNode.location = -800, 300 if scene.TLM_EngineProperties.tlm_lighting_mode == "indirect" or scene.TLM_EngineProperties.tlm_lighting_mode == "indirectAO": mixNode.blend_type = 'ADD' else: mixNode.blend_type = 'MULTIPLY' - mixNode.inputs[0].default_value = 1.0 + + if scene.TLM_EngineProperties.tlm_lighting_mode == "complete": + mixNode.inputs[0].default_value = 0.0 + else: + mixNode.inputs[0].default_value = 1.0 UVLightmap = node_tree.nodes.new(type="ShaderNodeUVMap") - UVLightmap.uv_map = "UVMap_Lightmap" + + if not obj.TLM_ObjectProperties.tlm_use_default_channel: + uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel + else: + uv_channel = "UVMap_Lightmap" + + UVLightmap.uv_map = uv_channel + UVLightmap.name = "Lightmap_UV" - UVLightmap.location = -1000, 300 + UVLightmap.location = -1500, 300 if(scene.TLM_SceneProperties.tlm_decoder_setup): if scene.TLM_SceneProperties.tlm_encoding_device == "CPU": @@ -196,7 +196,7 @@ def apply_materials(): baseColorValue = mainNode.inputs[0].default_value baseColorNode = node_tree.nodes.new(type="ShaderNodeRGB") baseColorNode.outputs[0].default_value = baseColorValue - baseColorNode.location = ((mainNode.location[0] - 500, mainNode.location[1] - 300)) + baseColorNode.location = ((mainNode.location[0] - 1100, mainNode.location[1] - 300)) baseColorNode.name = "Lightmap_BasecolorNode_A" else: baseColorNode = mainNode.inputs[0].links[0].from_node @@ -235,13 +235,19 @@ def apply_materials(): mat.node_tree.links.new(mixNode.outputs[0], mainNode.inputs[0]) #Connect mixnode to pbr node mat.node_tree.links.new(UVLightmap.outputs[0], lightmapNode.inputs[0]) #Connect uvnode to lightmapnode + #If skip metallic + if scene.TLM_SceneProperties.tlm_metallic_clamp == "skip": + if mainNode.inputs[4].default_value > 0.1: #DELIMITER + moutput = mainNode.inputs[0].links[0].from_node + mat.node_tree.links.remove(moutput.outputs[0].links[0]) + def exchangeLightmapsToPostfix(ext_postfix, new_postfix, formatHDR=".hdr"): if bpy.context.scene.TLM_SceneProperties.tlm_verbose: print(ext_postfix, new_postfix, formatHDR) - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: for slot in obj.material_slots: mat = slot.material @@ -267,6 +273,53 @@ def exchangeLightmapsToPostfix(ext_postfix, new_postfix, formatHDR=".hdr"): for image in bpy.data.images: image.reload() +def applyAOPass(): + + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + for slot in obj.material_slots: + mat = slot.material + node_tree = mat.node_tree + nodes = mat.node_tree.nodes + + for node in nodes: + if node.name == "Baked Image" or node.name == "TLM_Lightmap": + + filepath = bpy.data.filepath + dirpath = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir) + + LightmapPath = node.image.filepath_raw + + filebase = os.path.basename(LightmapPath) + filename = os.path.splitext(filebase)[0] + extension = os.path.splitext(filebase)[1] + AOImagefile = filename[:-4] + "_ao" + AOImagePath = os.path.join(dirpath, AOImagefile + extension) + + AOMap = nodes.new('ShaderNodeTexImage') + AOMap.name = "TLM_AOMap" + AOImage = bpy.data.images.load(AOImagePath) + AOMap.image = AOImage + AOMap.location = -800, 0 + + AOMult = nodes.new(type="ShaderNodeMixRGB") + AOMult.name = "TLM_AOMult" + AOMult.blend_type = 'MULTIPLY' + AOMult.inputs[0].default_value = 1.0 + AOMult.location = -300, 300 + + multyNode = nodes["Lightmap_Multiplication"] + mainNode = nodes["Principled BSDF"] + UVMapNode = nodes["Lightmap_UV"] + + node_tree.links.remove(multyNode.outputs[0].links[0]) + + node_tree.links.new(multyNode.outputs[0], AOMult.inputs[1]) + node_tree.links.new(AOMap.outputs[0], AOMult.inputs[2]) + node_tree.links.new(AOMult.outputs[0], mainNode.inputs[0]) + node_tree.links.new(UVMapNode.outputs[0], AOMap.inputs[0]) + def load_library(asset_name): scriptDir = os.path.dirname(os.path.realpath(__file__)) diff --git a/blender/arm/lightmapper/utility/cycles/prepare.py b/blender/arm/lightmapper/utility/cycles/prepare.py index 6e5bd70f..6222d18c 100644 --- a/blender/arm/lightmapper/utility/cycles/prepare.py +++ b/blender/arm/lightmapper/utility/cycles/prepare.py @@ -1,4 +1,4 @@ -import bpy +import bpy, math from . import cache from .. utility import * @@ -31,13 +31,16 @@ def configure_lights(): def configure_meshes(self): - for obj in bpy.data.objects: - if obj.type == "MESH": + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Configuring meshes") + + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: cache.backup_material_restore(obj) - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: cache.backup_material_rename(obj) @@ -59,9 +62,18 @@ def configure_meshes(self): scene = bpy.context.scene - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + + if scene.TLM_SceneProperties.tlm_apply_on_unwrap: + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Applying transform to: " + obj.name) + bpy.context.view_layer.objects.active = obj + obj.select_set(True) + bpy.ops.object.transform_apply(location=False, rotation=True, scale=True) + + obj.hide_select = False #Remember to toggle this back for slot in obj.material_slots: if "." + slot.name + '_Original' in bpy.data.materials: if bpy.context.scene.TLM_SceneProperties.tlm_verbose: @@ -77,33 +89,35 @@ def configure_meshes(self): bpy.ops.object.select_all(action='DESELECT') - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": uv_layers = obj.data.uv_layers - if not "UVMap_Lightmap" in uv_layers: + + if not obj.TLM_ObjectProperties.tlm_use_default_channel: + uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel + else: + uv_channel = "UVMap_Lightmap" + + if not uv_channel in uv_layers: if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("UVMap made A") - uvmap = uv_layers.new(name="UVMap_Lightmap") + print("UV map created for object: " + obj.name) + uvmap = uv_layers.new(name=uv_channel) uv_layers.active_index = len(uv_layers) - 1 else: - print("Existing found...skipping") + print("Existing UV map found for object: " + obj.name) for i in range(0, len(uv_layers)): if uv_layers[i].name == 'UVMap_Lightmap': uv_layers.active_index = i - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Lightmap shift A") break atlas_items.append(obj) obj.select_set(True) - if scene.TLM_SceneProperties.tlm_apply_on_unwrap: - bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) - if atlasgroup.tlm_atlas_lightmap_unwrap_mode == "SmartProject": if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Smart Project A for: " + str(atlas_items)) + print("Atlasgroup Smart Project for: " + str(atlas_items)) for obj in atlas_items: print(obj.name + ": Active UV: " + obj.data.uv_layers[obj.data.uv_layers.active_index].name) @@ -112,7 +126,12 @@ def configure_meshes(self): bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_all(action='SELECT') - bpy.ops.uv.smart_project(angle_limit=45.0, island_margin=atlasgroup.tlm_atlas_unwrap_margin, user_area_weight=1.0, use_aspect=True, stretch_to_bounds=False) + #API changes in 2.91 causes errors: + if (2, 91, 0) > bpy.app.version: + bpy.ops.uv.smart_project(angle_limit=45.0, island_margin=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin, user_area_weight=1.0, use_aspect=True, stretch_to_bounds=False) + else: + angle = math.radians(45.0) + bpy.ops.uv.smart_project(angle_limit=angle, island_margin=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin, area_weight=1.0, correct_aspect=True, scale_to_bounds=False) bpy.ops.mesh.select_all(action='DESELECT') bpy.ops.object.mode_set(mode='OBJECT') elif atlasgroup.tlm_atlas_lightmap_unwrap_mode == "Lightmap": @@ -123,8 +142,9 @@ def configure_meshes(self): bpy.ops.object.mode_set(mode='OBJECT') elif atlasgroup.tlm_atlas_lightmap_unwrap_mode == "Xatlas": + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Temporary skip: COPYING SMART PROJECT") + print("Using Xatlas on Atlas Group: " + atlas) for obj in atlas_items: obj.select_set(True) @@ -139,176 +159,213 @@ def configure_meshes(self): else: if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Copied Existing A") + print("Copied Existing UV Map for Atlas Group: " + atlas) - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: iterNum = iterNum + 1 - for obj in bpy.data.objects: - if obj.type == "MESH": - if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + for obj in bpy.context.scene.objects: + if obj.name in bpy.context.view_layer.objects: #Possible fix for view layer error + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: - objWasHidden = False + objWasHidden = False - #For some reason, a Blender bug might prevent invisible objects from being smart projected - #We will turn the object temporarily visible - obj.hide_viewport = False - obj.hide_set(False) + #For some reason, a Blender bug might prevent invisible objects from being smart projected + #We will turn the object temporarily visible + obj.hide_viewport = False + obj.hide_set(False) - currentIterNum = currentIterNum + 1 + currentIterNum = currentIterNum + 1 - #Configure selection - bpy.ops.object.select_all(action='DESELECT') - bpy.context.view_layer.objects.active = obj - obj.select_set(True) - obs = bpy.context.view_layer.objects - active = obs.active + #Configure selection + bpy.ops.object.select_all(action='DESELECT') + bpy.context.view_layer.objects.active = obj + obj.select_set(True) - #Provide material if none exists - preprocess_material(obj, scene) + obs = bpy.context.view_layer.objects + active = obs.active - #UV Layer management here - if not obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": - uv_layers = obj.data.uv_layers - if not "UVMap_Lightmap" in uv_layers: - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("UVMap made B") - uvmap = uv_layers.new(name="UVMap_Lightmap") - uv_layers.active_index = len(uv_layers) - 1 + #Provide material if none exists + preprocess_material(obj, scene) - #If lightmap - if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "Lightmap": - if scene.TLM_SceneProperties.tlm_apply_on_unwrap: - bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) - bpy.ops.uv.lightmap_pack('EXEC_SCREEN', PREF_CONTEXT='ALL_FACES', PREF_MARGIN_DIV=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin) - - #If smart project - elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "SmartProject": + #UV Layer management here + if not obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": + uv_layers = obj.data.uv_layers + + if not obj.TLM_ObjectProperties.tlm_use_default_channel: + uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel + else: + uv_channel = "UVMap_Lightmap" + + if not uv_channel in uv_layers: if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Smart Project B") - if scene.TLM_SceneProperties.tlm_apply_on_unwrap: - bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) - bpy.ops.object.select_all(action='DESELECT') - obj.select_set(True) - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.mesh.select_all(action='DESELECT') - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.uv.smart_project(angle_limit=45.0, island_margin=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin, user_area_weight=1.0, use_aspect=True, stretch_to_bounds=False) - - elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "Xatlas": + print("UV map created for obj: " + obj.name) + uvmap = uv_layers.new(name=uv_channel) + uv_layers.active_index = len(uv_layers) - 1 - if scene.TLM_SceneProperties.tlm_apply_on_unwrap: - bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) + #If lightmap + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "Lightmap": + bpy.ops.uv.lightmap_pack('EXEC_SCREEN', PREF_CONTEXT='ALL_FACES', PREF_MARGIN_DIV=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin) - #import blender_xatlas - #blender_xatlas.Unwrap_Lightmap_Group_Xatlas_2(bpy.context) + #If smart project + elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "SmartProject": - #bpy.ops.object.setup_unwrap() - Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj) - - elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": - - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("ATLAS GROUP: " + obj.TLM_ObjectProperties.tlm_atlas_pointer) - - else: #if copy existing - - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Copied Existing B") - - else: - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Existing found...skipping") - for i in range(0, len(uv_layers)): - if uv_layers[i].name == 'UVMap_Lightmap': - uv_layers.active_index = i if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Lightmap shift B") - break + print("Smart Project B") + bpy.ops.object.select_all(action='DESELECT') + obj.select_set(True) + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='SELECT') + #API changes in 2.91 causes errors: + if (2, 91, 0) > bpy.app.version: + bpy.ops.uv.smart_project(angle_limit=45.0, island_margin=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin, user_area_weight=1.0, use_aspect=True, stretch_to_bounds=False) + else: + angle = math.radians(45.0) + bpy.ops.uv.smart_project(angle_limit=angle, island_margin=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin, area_weight=1.0, correct_aspect=True, scale_to_bounds=False) + bpy.ops.mesh.select_all(action='DESELECT') + bpy.ops.object.mode_set(mode='OBJECT') + + elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "Xatlas": + + Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj) - #Sort out nodes - for slot in obj.material_slots: + elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": - nodetree = slot.material.node_tree + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("ATLAS GROUP: " + obj.TLM_ObjectProperties.tlm_atlas_pointer) + + else: #if copy existing - outputNode = nodetree.nodes[0] #Presumed to be material output node + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Copied Existing UV Map for object: " + obj.name) - if(outputNode.type != "OUTPUT_MATERIAL"): + else: + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Existing UV map found for obj: " + obj.name) + for i in range(0, len(uv_layers)): + if uv_layers[i].name == uv_channel: + uv_layers.active_index = i + break + + #print(x) + + #Sort out nodes + for slot in obj.material_slots: + + nodetree = slot.material.node_tree + + outputNode = nodetree.nodes[0] #Presumed to be material output node + + if(outputNode.type != "OUTPUT_MATERIAL"): + for node in nodetree.nodes: + if node.type == "OUTPUT_MATERIAL": + outputNode = node + break + + mainNode = outputNode.inputs[0].links[0].from_node + + if mainNode.type not in ['BSDF_PRINCIPLED','BSDF_DIFFUSE','GROUP']: + + #TODO! FIND THE PRINCIPLED PBR + self.report({'INFO'}, "The primary material node is not supported. Seeking first principled.") + + if len(find_node_by_type(nodetree.nodes, Node_Types.pbr_node)) > 0: + mainNode = find_node_by_type(nodetree.nodes, Node_Types.pbr_node)[0] + else: + self.report({'INFO'}, "No principled found. Seeking diffuse") + if len(find_node_by_type(nodetree.nodes, Node_Types.diffuse)) > 0: + mainNode = find_node_by_type(nodetree.nodes, Node_Types.diffuse)[0] + else: + self.report({'INFO'}, "No supported nodes. Continuing anyway.") + + if mainNode.type == 'GROUP': + if mainNode.node_tree != "Armory PBR": + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("The material group is not supported!") + + if (mainNode.type == "BSDF_PRINCIPLED"): + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("BSDF_Principled") + if scene.TLM_EngineProperties.tlm_directional_mode == "None": + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Directional mode") + if not len(mainNode.inputs[19].links) == 0: + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("NOT LEN 0") + ninput = mainNode.inputs[19].links[0] + noutput = mainNode.inputs[19].links[0].from_node + nodetree.links.remove(noutput.outputs[0].links[0]) + + #Clamp metallic + if bpy.context.scene.TLM_SceneProperties.tlm_metallic_clamp == "limit": + MainMetNodeSocket = mainNode.inputs[4] + if not len(MainMetNodeSocket.links) == 0: + nodes = nodetree.nodes + MetClampNode = nodes.new('ShaderNodeClamp') + MetClampNode.location = (-200,150) + MetClampNode.inputs[2].default_value = 0.9 + minput = mainNode.inputs[4].links[0] #Metal input socket + moutput = mainNode.inputs[4].links[0].from_node #Metal output node + nodetree.links.remove(moutput.outputs[0].links[0]) #Works + nodetree.links.new(moutput.outputs[0], MetClampNode.inputs[0]) #minput node to clamp node + nodetree.links.new(MetClampNode.outputs[0],MainMetNodeSocket) #clamp node to metinput + else: + if mainNode.inputs[4].default_value > 0.9: + mainNode.inputs[4].default_value = 0.9 + elif bpy.context.scene.TLM_SceneProperties.tlm_metallic_clamp == "zero": + MainMetNodeSocket = mainNode.inputs[4] + if not len(MainMetNodeSocket.links) == 0: + nodes = nodetree.nodes + MetClampNode = nodes.new('ShaderNodeClamp') + MetClampNode.location = (-200,150) + MetClampNode.inputs[2].default_value = 0.0 + minput = mainNode.inputs[4].links[0] #Metal input socket + moutput = mainNode.inputs[4].links[0].from_node #Metal output node + nodetree.links.remove(moutput.outputs[0].links[0]) #Works + nodetree.links.new(moutput.outputs[0], MetClampNode.inputs[0]) #minput node to clamp node + nodetree.links.new(MetClampNode.outputs[0],MainMetNodeSocket) #clamp node to metinput + else: + mainNode.inputs[4].default_value = 0.0 + + if (mainNode.type == "BSDF_DIFFUSE"): + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("BSDF_Diffuse") + + # if (mainNode.type == "BSDF_DIFFUSE"): + # if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + # print("BSDF_Diffuse") + + #TODO FIX THIS PART! + #THIS IS USED IN CASES WHERE FOR SOME REASON THE USER FORGETS TO CONNECT SOMETHING INTO THE OUTPUT MATERIAL + for slot in obj.material_slots: + + nodetree = bpy.data.materials[slot.name].node_tree + nodes = nodetree.nodes + + #First search to get the first output material type for node in nodetree.nodes: if node.type == "OUTPUT_MATERIAL": - outputNode = node + mainNode = node break - mainNode = outputNode.inputs[0].links[0].from_node + #Fallback to get search + if not mainNode.type == "OUTPUT_MATERIAL": + mainNode = nodetree.nodes.get("Material Output") - if mainNode.type not in ['BSDF_PRINCIPLED','BSDF_DIFFUSE','GROUP']: + #Last resort to first node in list + if not mainNode.type == "OUTPUT_MATERIAL": + mainNode = nodetree.nodes[0].inputs[0].links[0].from_node - #TODO! FIND THE PRINCIPLED PBR - self.report({'INFO'}, "The primary material node is not supported. Seeking first principled.") + # for node in nodes: + # if "LM" in node.name: + # nodetree.links.new(node.outputs[0], mainNode.inputs[0]) - if len(find_node_by_type(nodetree.nodes, Node_Types.pbr_node)) > 0: - mainNode = find_node_by_type(nodetree.nodes, Node_Types.pbr_node)[0] - else: - self.report({'INFO'}, "No principled found. Seeking diffuse") - if len(find_node_by_type(nodetree.nodes, Node_Types.diffuse)) > 0: - mainNode = find_node_by_type(nodetree.nodes, Node_Types.diffuse)[0] - else: - self.report({'INFO'}, "No supported nodes. Continuing anyway.") - - if mainNode.type == 'GROUP': - if mainNode.node_tree != "Armory PBR": - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("The material group is not supported!") - - if (mainNode.type == "BSDF_PRINCIPLED"): - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("BSDF_Principled") - if scene.TLM_EngineProperties.tlm_directional_mode == "None": - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Directional mode") - if not len(mainNode.inputs[19].links) == 0: - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("NOT LEN 0") - ninput = mainNode.inputs[19].links[0] - noutput = mainNode.inputs[19].links[0].from_node - nodetree.links.remove(noutput.outputs[0].links[0]) - - #Clamp metallic - if(mainNode.inputs[4].default_value == 1): - mainNode.inputs[4].default_value = 0.0 - - if (mainNode.type == "BSDF_DIFFUSE"): - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("BSDF_Diffuse") - - for slot in obj.material_slots: - - nodetree = bpy.data.materials[slot.name].node_tree - nodes = nodetree.nodes - - #First search to get the first output material type - for node in nodetree.nodes: - if node.type == "OUTPUT_MATERIAL": - mainNode = node - break - - #Fallback to get search - if not mainNode.type == "OUTPUT_MATERIAL": - mainNode = nodetree.nodes.get("Material Output") - - #Last resort to first node in list - if not mainNode.type == "OUTPUT_MATERIAL": - mainNode = nodetree.nodes[0].inputs[0].links[0].from_node - - for node in nodes: - if "LM" in node.name: - nodetree.links.new(node.outputs[0], mainNode.inputs[0]) - - for node in nodes: - if "Lightmap" in node.name: - nodes.remove(node) + # for node in nodes: + # if "Lightmap" in node.name: + # nodes.remove(node) def preprocess_material(obj, scene): if len(obj.material_slots) == 0: @@ -537,7 +594,7 @@ def store_existing(prev_container): selected = [] - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: if obj.select_get(): selected.append(obj.name) diff --git a/blender/arm/lightmapper/utility/denoiser/integrated.py b/blender/arm/lightmapper/utility/denoiser/integrated.py index e3061f30..38c1cabc 100644 --- a/blender/arm/lightmapper/utility/denoiser/integrated.py +++ b/blender/arm/lightmapper/utility/denoiser/integrated.py @@ -22,7 +22,7 @@ class TLM_Integrated_Denoise: bpy.ops.object.camera_add() #Just select the first camera we find, needed for the compositor - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: if obj.type == "CAMERA": bpy.context.scene.camera = obj return diff --git a/blender/arm/lightmapper/utility/encoding.py b/blender/arm/lightmapper/utility/encoding.py index 92a8e8d5..f716f342 100644 --- a/blender/arm/lightmapper/utility/encoding.py +++ b/blender/arm/lightmapper/utility/encoding.py @@ -332,6 +332,205 @@ def encodeImageRGBDGPU(image, maxRange, outDir, quality): #Todo - Find a way to save #bpy.ops.image.save_all_modified() +#TODO - FINISH THIS +def encodeImageRGBMGPU(image, maxRange, outDir, quality): + input_image = bpy.data.images[image.name] + image_name = input_image.name + + offscreen = gpu.types.GPUOffScreen(input_image.size[0], input_image.size[1]) + + image = input_image + + vertex_shader = ''' + + uniform mat4 ModelViewProjectionMatrix; + + in vec2 texCoord; + in vec2 pos; + out vec2 texCoord_interp; + + void main() + { + //gl_Position = ModelViewProjectionMatrix * vec4(pos.xy, 0.0f, 1.0f); + //gl_Position.z = 1.0; + gl_Position = vec4(pos.xy, 100, 100); + texCoord_interp = texCoord; + } + + ''' + fragment_shader = ''' + in vec2 texCoord_interp; + out vec4 fragColor; + + uniform sampler2D image; + + //Code from here: https://github.com/BabylonJS/Babylon.js/blob/master/src/Shaders/ShadersInclude/helperFunctions.fx + + const float PI = 3.1415926535897932384626433832795; + const float HALF_MIN = 5.96046448e-08; // Smallest positive half. + + const float LinearEncodePowerApprox = 2.2; + const float GammaEncodePowerApprox = 1.0 / LinearEncodePowerApprox; + const vec3 LuminanceEncodeApprox = vec3(0.2126, 0.7152, 0.0722); + + const float Epsilon = 0.0000001; + #define saturate(x) clamp(x, 0.0, 1.0) + + float maxEps(float x) { + return max(x, Epsilon); + } + + float toLinearSpace(float color) + { + return pow(color, LinearEncodePowerApprox); + } + + vec3 toLinearSpace(vec3 color) + { + return pow(color, vec3(LinearEncodePowerApprox)); + } + + vec4 toLinearSpace(vec4 color) + { + return vec4(pow(color.rgb, vec3(LinearEncodePowerApprox)), color.a); + } + + vec3 toGammaSpace(vec3 color) + { + return pow(color, vec3(GammaEncodePowerApprox)); + } + + vec4 toGammaSpace(vec4 color) + { + return vec4(pow(color.rgb, vec3(GammaEncodePowerApprox)), color.a); + } + + float toGammaSpace(float color) + { + return pow(color, GammaEncodePowerApprox); + } + + float square(float value) + { + return value * value; + } + + // Check if configurable value is needed. + const float rgbdMaxRange = 255.0; + + vec4 toRGBM(vec3 color) { + + vec4 rgbm; + color *= 1.0/6.0; + rgbm.a = saturate( max( max( color.r, color.g ), max( color.b, 1e-6 ) ) ); + rgbm.a = clamp(floor(D) / 255.0, 0., 1.); + rgbm.rgb = color / rgbm.a; + + return + + float maxRGB = maxEps(max(color.r, max(color.g, color.b))); + float D = max(rgbdMaxRange / maxRGB, 1.); + D = clamp(floor(D) / 255.0, 0., 1.); + vec3 rgb = color.rgb * D; + + // Helps with png quantization. + rgb = toGammaSpace(rgb); + + return vec4(rgb, D); + } + + vec3 fromRGBD(vec4 rgbd) { + // Helps with png quantization. + rgbd.rgb = toLinearSpace(rgbd.rgb); + + // return rgbd.rgb * ((rgbdMaxRange / 255.0) / rgbd.a); + + return rgbd.rgb / rgbd.a; + } + + void main() + { + + fragColor = toRGBM(texture(image, texCoord_interp).rgb); + + } + + ''' + + x_screen = 0 + off_x = -100 + off_y = -100 + y_screen_flip = 0 + sx = 200 + sy = 200 + + vertices = ( + (x_screen + off_x, y_screen_flip - off_y), + (x_screen + off_x, y_screen_flip - sy - off_y), + (x_screen + off_x + sx, y_screen_flip - sy - off_y), + (x_screen + off_x + sx, y_screen_flip - off_x)) + + if input_image.colorspace_settings.name != 'Linear': + input_image.colorspace_settings.name = 'Linear' + + # Removing .exr or .hdr prefix + if image_name[-4:] == '.exr' or image_name[-4:] == '.hdr': + image_name = image_name[:-4] + + target_image = bpy.data.images.get(image_name + '_encoded') + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print(image_name + '_encoded') + if not target_image: + target_image = bpy.data.images.new( + name = image_name + '_encoded', + width = input_image.size[0], + height = input_image.size[1], + alpha = True, + float_buffer = False + ) + + shader = gpu.types.GPUShader(vertex_shader, fragment_shader) + batch = batch_for_shader( + shader, 'TRI_FAN', + { + "pos": vertices, + "texCoord": ((0, 1), (0, 0), (1, 0), (1, 1)), + }, + ) + + if image.gl_load(): + raise Exception() + + with offscreen.bind(): + bgl.glActiveTexture(bgl.GL_TEXTURE0) + bgl.glBindTexture(bgl.GL_TEXTURE_2D, image.bindcode) + + shader.bind() + shader.uniform_int("image", 0) + batch.draw(shader) + + buffer = bgl.Buffer(bgl.GL_BYTE, input_image.size[0] * input_image.size[1] * 4) + bgl.glReadBuffer(bgl.GL_BACK) + bgl.glReadPixels(0, 0, input_image.size[0], input_image.size[1], bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, buffer) + + offscreen.free() + + target_image.pixels = [v / 255 for v in buffer] + input_image = target_image + + #Save LogLuv + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print(input_image.name) + input_image.filepath_raw = outDir + "/" + input_image.name + ".png" + #input_image.filepath_raw = outDir + "_encoded.png" + input_image.file_format = "PNG" + bpy.context.scene.render.image_settings.quality = quality + #input_image.save_render(filepath = input_image.filepath_raw, scene = bpy.context.scene) + input_image.save() + + #Todo - Find a way to save + #bpy.ops.image.save_all_modified() + def encodeImageRGBMCPU(image, maxRange, outDir, quality): input_image = bpy.data.images[image.name] image_name = input_image.name @@ -431,21 +630,6 @@ def encodeImageRGBDCPU(image, maxRange, outDir, quality): result_pixel[i+1] = math.pow(result_pixel[i+1] * D, 1/2.2) result_pixel[i+2] = math.pow(result_pixel[i+2] * D, 1/2.2) result_pixel[i+3] = D - - - # for i in range(0,num_pixels,4): - - # m = saturate(max(result_pixel[i], result_pixel[i+1], result_pixel[i+2], 1e-6)) - # d = max(maxRange / m, 1) - # #d = saturate(math.floor(d) / 255.0) - # d = np.clip((math.floor(d) / 255.0), 0.0, 1.0) - - # #TODO TO GAMMA SPACE - - # result_pixel[i] = math.pow(result_pixel[i] * d * 255 / maxRange, 1/2.2) - # result_pixel[i+1] = math.pow(result_pixel[i+1] * d * 255 / maxRange, 1/2.2) - # result_pixel[i+2] = math.pow(result_pixel[i+2] * d * 255 / maxRange, 1/2.2) - # result_pixel[i+3] = d target_image.pixels = result_pixel @@ -457,25 +641,4 @@ def encodeImageRGBDCPU(image, maxRange, outDir, quality): input_image.filepath_raw = outDir + "/" + input_image.name + ".png" input_image.file_format = "PNG" bpy.context.scene.render.image_settings.quality = quality - input_image.save() - - # const float rgbdMaxRange = 255.0; - - # vec4 toRGBD(vec3 color) { - # float maxRGB = maxEps(max(color.r, max(color.g, color.b))); - # float D = max(rgbdMaxRange / maxRGB, 1.); - # D = clamp(floor(D) / 255.0, 0., 1.); - # vec3 rgb = color.rgb * D; - - # // Helps with png quantization. - # rgb = toGammaSpace(rgb); - - # return vec4(rgb, D); - # } - - # const float Epsilon = 0.0000001; - # #define saturate(x) clamp(x, 0.0, 1.0) - - # float maxEps(float x) { - # return max(x, Epsilon); - # } \ No newline at end of file + input_image.save() \ No newline at end of file diff --git a/blender/arm/lightmapper/utility/filtering/opencv.py b/blender/arm/lightmapper/utility/filtering/opencv.py index 501b2b3d..c6b1b557 100644 --- a/blender/arm/lightmapper/utility/filtering/opencv.py +++ b/blender/arm/lightmapper/utility/filtering/opencv.py @@ -62,7 +62,7 @@ class TLM_CV_Filtering: #SEAM TESTING# ##################### - if obj_name in bpy.data.objects: + if obj_name in bpy.context.scene.objects: override = bpy.data.objects[obj_name].TLM_ObjectProperties.tlm_mesh_filter_override elif obj_name in scene.TLM_AtlasList: override = False diff --git a/blender/arm/lightmapper/utility/gui/Viewport.py b/blender/arm/lightmapper/utility/gui/Viewport.py new file mode 100644 index 00000000..be1e8d2b --- /dev/null +++ b/blender/arm/lightmapper/utility/gui/Viewport.py @@ -0,0 +1,67 @@ +import bpy, blf, bgl, os, gpu +from gpu_extras.batch import batch_for_shader + +class ViewportDraw: + + def __init__(self, context, text): + + bakefile = "TLM_Overlay.png" + scriptDir = os.path.dirname(os.path.realpath(__file__)) + bakefile_path = os.path.abspath(os.path.join(scriptDir, '..', '..', 'assets/' + bakefile)) + + image_name = "TLM_Overlay.png" + + bpy.ops.image.open(filepath=bakefile_path) + + print("Self path: " + bakefile_path) + + image = bpy.data.images[image_name] + + x = 15 + y = 15 + w = 400 + h = 200 + + self.shader = gpu.shader.from_builtin('2D_IMAGE') + self.batch = batch_for_shader( + self.shader, 'TRI_FAN', + { + "pos": ((x, y), (x+w, y), (x+w, y+h), (x, y+h)), + "texCoord": ((0, 0), (1, 0), (1, 1), (0, 1)), + }, + ) + + if image.gl_load(): + raise Exception() + + self.text = text + self.image = image + #self.handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_text_callback, (context,), 'WINDOW', 'POST_PIXEL') + self.handle2 = bpy.types.SpaceView3D.draw_handler_add(self.draw_image_callback, (context,), 'WINDOW', 'POST_PIXEL') + + def draw_text_callback(self, context): + + font_id = 0 + blf.position(font_id, 15, 15, 0) + blf.size(font_id, 20, 72) + blf.draw(font_id, "%s" % (self.text)) + + def draw_image_callback(self, context): + + if self.image: + bgl.glEnable(bgl.GL_BLEND) + bgl.glActiveTexture(bgl.GL_TEXTURE0) + bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.image.bindcode) + + self.shader.bind() + self.shader.uniform_int("image", 0) + self.batch.draw(self.shader) + bgl.glDisable(bgl.GL_BLEND) + + def update_text(self, text): + + self.text = text + + def remove_handle(self): + #bpy.types.SpaceView3D.draw_handler_remove(self.handle, 'WINDOW') + bpy.types.SpaceView3D.draw_handler_remove(self.handle2, 'WINDOW') \ No newline at end of file diff --git a/blender/arm/lightmapper/utility/luxcore/setup.py b/blender/arm/lightmapper/utility/luxcore/setup.py new file mode 100644 index 00000000..13bfc57b --- /dev/null +++ b/blender/arm/lightmapper/utility/luxcore/setup.py @@ -0,0 +1,259 @@ +import bpy + +from .. utility import * + +def init(self, prev_container): + + #TODO - JSON classes + export.scene = """scene.camera.cliphither = 0.1 +scene.camera.clipyon = 100 +scene.camera.shutteropen = 0 +scene.camera.shutterclose = 1 +scene.camera.autovolume.enable = 1 +scene.camera.lookat.orig = 7.358891 -6.925791 4.958309 +scene.camera.lookat.target = 6.707333 -6.31162 4.513038 +scene.camera.up = -0.3240135 0.3054208 0.8953956 +scene.camera.screenwindow = -1 1 -0.5625 0.5625 +scene.camera.lensradius = 0 +scene.camera.focaldistance = 10 +scene.camera.autofocus.enable = 0 +scene.camera.type = "perspective" +scene.camera.oculusrift.barrelpostpro.enable = 0 +scene.camera.fieldofview = 39.59776 +scene.camera.bokeh.blades = 0 +scene.camera.bokeh.power = 3 +scene.camera.bokeh.distribution.type = "NONE" +scene.camera.bokeh.scale.x = 0.7071068 +scene.camera.bokeh.scale.y = 0.7071068 +scene.lights.__WORLD_BACKGROUND_LIGHT__.gain = 2e-05 2e-05 2e-05 +scene.lights.__WORLD_BACKGROUND_LIGHT__.transformation = 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 +scene.lights.__WORLD_BACKGROUND_LIGHT__.id = 0 +scene.lights.__WORLD_BACKGROUND_LIGHT__.temperature = -1 +scene.lights.__WORLD_BACKGROUND_LIGHT__.temperature.normalize = 0 +scene.lights.__WORLD_BACKGROUND_LIGHT__.visibility.indirect.diffuse.enable = 1 +scene.lights.__WORLD_BACKGROUND_LIGHT__.visibility.indirect.glossy.enable = 1 +scene.lights.__WORLD_BACKGROUND_LIGHT__.visibility.indirect.specular.enable = 1 +scene.lights.__WORLD_BACKGROUND_LIGHT__.type = "sky2" +scene.lights.__WORLD_BACKGROUND_LIGHT__.dir = 0 0 1 +scene.lights.__WORLD_BACKGROUND_LIGHT__.turbidity = 2.2 +scene.lights.__WORLD_BACKGROUND_LIGHT__.groundalbedo = 0.5 0.5 0.5 +scene.lights.__WORLD_BACKGROUND_LIGHT__.ground.enable = 0 +scene.lights.__WORLD_BACKGROUND_LIGHT__.ground.color = 0.5 0.5 0.5 +scene.lights.__WORLD_BACKGROUND_LIGHT__.ground.autoscale = 1 +scene.lights.__WORLD_BACKGROUND_LIGHT__.distribution.width = 512 +scene.lights.__WORLD_BACKGROUND_LIGHT__.distribution.height = 256 +scene.lights.__WORLD_BACKGROUND_LIGHT__.visibilitymapcache.enable = 0 +scene.lights.2382361116072.gain = 1 1 1 +scene.lights.2382361116072.transformation = -0.2908646 0.9551712 -0.05518906 0 -0.7711008 -0.1998834 0.6045247 0 0.5663932 0.2183912 0.7946723 0 4.076245 1.005454 5.903862 1 +scene.lights.2382361116072.id = 0 +scene.lights.2382361116072.temperature = -1 +scene.lights.2382361116072.temperature.normalize = 0 +scene.lights.2382361116072.type = "sphere" +scene.lights.2382361116072.color = 1 1 1 +scene.lights.2382361116072.power = 0 +scene.lights.2382361116072.normalizebycolor = 0 +scene.lights.2382361116072.efficency = 0 +scene.lights.2382361116072.position = 0 0 0 +scene.lights.2382361116072.radius = 0.1 +scene.materials.Material2382357175256.type = "disney" +scene.materials.Material2382357175256.basecolor = "0.7 0.7 0.7" +scene.materials.Material2382357175256.subsurface = "0" +scene.materials.Material2382357175256.roughness = "0.2" +scene.materials.Material2382357175256.metallic = "0" +scene.materials.Material2382357175256.specular = "0.5" +scene.materials.Material2382357175256.speculartint = "0" +scene.materials.Material2382357175256.clearcoat = "0" +scene.materials.Material2382357175256.clearcoatgloss = "1" +scene.materials.Material2382357175256.anisotropic = "0" +scene.materials.Material2382357175256.sheen = "0" +scene.materials.Material2382357175256.sheentint = "0" +scene.materials.Material2382357175256.transparency.shadow = 0 0 0 +scene.materials.Material2382357175256.id = 3364224 +scene.materials.Material2382357175256.emission.gain = 1 1 1 +scene.materials.Material2382357175256.emission.power = 0 +scene.materials.Material2382357175256.emission.normalizebycolor = 1 +scene.materials.Material2382357175256.emission.efficency = 0 +scene.materials.Material2382357175256.emission.theta = 90 +scene.materials.Material2382357175256.emission.id = 0 +scene.materials.Material2382357175256.emission.importance = 1 +scene.materials.Material2382357175256.emission.temperature = -1 +scene.materials.Material2382357175256.emission.temperature.normalize = 0 +scene.materials.Material2382357175256.emission.directlightsampling.type = "AUTO" +scene.materials.Material2382357175256.visibility.indirect.diffuse.enable = 1 +scene.materials.Material2382357175256.visibility.indirect.glossy.enable = 1 +scene.materials.Material2382357175256.visibility.indirect.specular.enable = 1 +scene.materials.Material2382357175256.shadowcatcher.enable = 0 +scene.materials.Material2382357175256.shadowcatcher.onlyinfinitelights = 0 +scene.materials.Material2382357175256.photongi.enable = 1 +scene.materials.Material2382357175256.holdout.enable = 0 +scene.materials.Material__0012382357172440.type = "disney" +scene.materials.Material__0012382357172440.basecolor = "0.7 0.7 0.7" +scene.materials.Material__0012382357172440.subsurface = "0" +scene.materials.Material__0012382357172440.roughness = "0.2" +scene.materials.Material__0012382357172440.metallic = "0" +scene.materials.Material__0012382357172440.specular = "0.5" +scene.materials.Material__0012382357172440.speculartint = "0" +scene.materials.Material__0012382357172440.clearcoat = "0" +scene.materials.Material__0012382357172440.clearcoatgloss = "1" +scene.materials.Material__0012382357172440.anisotropic = "0" +scene.materials.Material__0012382357172440.sheen = "0" +scene.materials.Material__0012382357172440.sheentint = "0" +scene.materials.Material__0012382357172440.transparency.shadow = 0 0 0 +scene.materials.Material__0012382357172440.id = 6728256 +scene.materials.Material__0012382357172440.emission.gain = 1 1 1 +scene.materials.Material__0012382357172440.emission.power = 0 +scene.materials.Material__0012382357172440.emission.normalizebycolor = 1 +scene.materials.Material__0012382357172440.emission.efficency = 0 +scene.materials.Material__0012382357172440.emission.theta = 90 +scene.materials.Material__0012382357172440.emission.id = 0 +scene.materials.Material__0012382357172440.emission.importance = 1 +scene.materials.Material__0012382357172440.emission.temperature = -1 +scene.materials.Material__0012382357172440.emission.temperature.normalize = 0 +scene.materials.Material__0012382357172440.emission.directlightsampling.type = "AUTO" +scene.materials.Material__0012382357172440.visibility.indirect.diffuse.enable = 1 +scene.materials.Material__0012382357172440.visibility.indirect.glossy.enable = 1 +scene.materials.Material__0012382357172440.visibility.indirect.specular.enable = 1 +scene.materials.Material__0012382357172440.shadowcatcher.enable = 0 +scene.materials.Material__0012382357172440.shadowcatcher.onlyinfinitelights = 0 +scene.materials.Material__0012382357172440.photongi.enable = 1 +scene.materials.Material__0012382357172440.holdout.enable = 0 +scene.objects.23823611086320.material = "Material2382357175256" +scene.objects.23823611086320.ply = "mesh-00000.ply" +scene.objects.23823611086320.camerainvisible = 0 +scene.objects.23823611086320.id = 1326487202 +scene.objects.23823611086320.appliedtransformation = 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 1 +scene.objects.23823611279760.material = "Material__0012382357172440" +scene.objects.23823611279760.ply = "mesh-00001.ply" +scene.objects.23823611279760.camerainvisible = 0 +scene.objects.23823611279760.id = 3772660237 +scene.objects.23823611279760.appliedtransformation = 5 0 0 0 0 5 0 0 0 0 5 0 0 0 0 1 +""" + + export.config = """context.verbose = 1 +accelerator.type = "AUTO" +accelerator.instances.enable = 1 +accelerator.motionblur.enable = 1 +accelerator.bvh.builder.type = "EMBREE_BINNED_SAH" +accelerator.bvh.treetype = 4 +accelerator.bvh.costsamples = 0 +accelerator.bvh.isectcost = 80 +accelerator.bvh.travcost = 10 +accelerator.bvh.emptybonus = 0.5 +scene.epsilon.min = "1e-05" +scene.epsilon.max = "0.1" +scene.file = "scene.scn" +images.scale = 1 +lightstrategy.type = "LOG_POWER" +native.threads.count = 8 +renderengine.type = "BAKECPU" +path.pathdepth.total = "7" +path.pathdepth.diffuse = "5" +path.pathdepth.glossy = "5" +path.pathdepth.specular = "6" +path.hybridbackforward.enable = "0" +path.hybridbackforward.partition = "0.8" +path.hybridbackforward.glossinessthreshold = "0.049" +path.russianroulette.depth = 3 +path.russianroulette.cap = 0.5 +path.clamping.variance.maxvalue = 0 +path.forceblackbackground.enable = "0" +sampler.type = "SOBOL" +sampler.imagesamples.enable = 1 +sampler.sobol.adaptive.strength = "0.9" +sampler.sobol.adaptive.userimportanceweight = 0.75 +sampler.sobol.bucketsize = "16" +sampler.sobol.tilesize = "16" +sampler.sobol.supersampling = "1" +sampler.sobol.overlapping = "1" +path.photongi.sampler.type = "METROPOLIS" +path.photongi.photon.maxcount = 100000000 +path.photongi.photon.maxdepth = 4 +path.photongi.photon.time.start = 0 +path.photongi.photon.time.end = -1 +path.photongi.visibility.lookup.radius = 0 +path.photongi.visibility.lookup.normalangle = 10 +path.photongi.visibility.targethitrate = 0.99 +path.photongi.visibility.maxsamplecount = 1048576 +path.photongi.glossinessusagethreshold = 0.05 +path.photongi.indirect.enabled = 0 +path.photongi.indirect.maxsize = 0 +path.photongi.indirect.haltthreshold = 0.05 +path.photongi.indirect.lookup.radius = 0 +path.photongi.indirect.lookup.normalangle = 10 +path.photongi.indirect.usagethresholdscale = 8 +path.photongi.indirect.filter.radiusscale = 3 +path.photongi.caustic.enabled = 0 +path.photongi.caustic.maxsize = 100000 +path.photongi.caustic.updatespp = 8 +path.photongi.caustic.updatespp.radiusreduction = 0.96 +path.photongi.caustic.updatespp.minradius = 0.003 +path.photongi.caustic.lookup.radius = 0.15 +path.photongi.caustic.lookup.normalangle = 10 +path.photongi.debug.type = "none" +path.photongi.persistent.file = "" +path.photongi.persistent.safesave = 1 +film.filter.type = "BLACKMANHARRIS" +film.filter.width = 2 +opencl.platform.index = -1 +film.width = 960 +film.height = 600 +film.safesave = 1 +film.noiseestimation.step = "32" +film.noiseestimation.warmup = "8" +film.noiseestimation.filter.scale = 4 +batch.haltnoisethreshold = 0.01 +batch.haltnoisethreshold.step = 64 +batch.haltnoisethreshold.warmup = 64 +batch.haltnoisethreshold.filter.enable = 1 +batch.haltnoisethreshold.stoprendering.enable = 1 +batch.halttime = "0" +batch.haltspp = 32 +film.outputs.safesave = 1 +film.outputs.0.type = "RGB_IMAGEPIPELINE" +film.outputs.0.filename = "RGB_IMAGEPIPELINE_0.png" +film.outputs.0.index = "0" +film.imagepipelines.000.0.type = "NOP" +film.imagepipelines.000.1.type = "TONEMAP_LINEAR" +film.imagepipelines.000.1.scale = "1" +film.imagepipelines.000.2.type = "GAMMA_CORRECTION" +film.imagepipelines.000.2.value = "2.2" +film.imagepipelines.000.radiancescales.0.enabled = "1" +film.imagepipelines.000.radiancescales.0.globalscale = "1" +film.imagepipelines.000.radiancescales.0.rgbscale = "1" "1" "1" +periodicsave.film.outputs.period = 0 +periodicsave.film.period = 0 +periodicsave.film.filename = "film.flm" +periodicsave.resumerendering.period = 0 +periodicsave.resumerendering.filename = "rendering.rsm" +resumerendering.filesafe = 1 +debug.renderconfig.parse.print = 0 +debug.scene.parse.print = 0 +screen.refresh.interval = 100 +screen.tool.type = "CAMERA_EDIT" +screen.tiles.pending.show = 1 +screen.tiles.converged.show = 0 +screen.tiles.notconverged.show = 0 +screen.tiles.passcount.show = 0 +screen.tiles.error.show = 0 +bake.minmapautosize = 64 +bake.maxmapautosize = 1024 +bake.powerof2autosize.enable = 1 +bake.skipexistingmapfiles = 1 +film.imagepipelines.1.0.type = "NOP" +bake.maps.0.type = "COMBINED" +bake.maps.0.filename = "23823611086320.exr" +bake.maps.0.imagepipelineindex = 1 +bake.maps.0.width = 512 +bake.maps.0.height = 512 +bake.maps.0.autosize.enabled = 1 +bake.maps.0.uvindex = 0 +bake.maps.0.objectnames = "23823611086320" +bake.maps.1.type = "COMBINED" +bake.maps.1.filename = "23823611279760.exr" +bake.maps.1.imagepipelineindex = 1 +bake.maps.1.width = 512 +bake.maps.1.height = 512 +bake.maps.1.autosize.enabled = 1 +bake.maps.1.uvindex = 0 +bake.maps.1.objectnames = "23823611279760" +""" \ No newline at end of file diff --git a/blender/arm/lightmapper/utility/octane/configure.py b/blender/arm/lightmapper/utility/octane/configure.py new file mode 100644 index 00000000..ba6641ab --- /dev/null +++ b/blender/arm/lightmapper/utility/octane/configure.py @@ -0,0 +1,243 @@ +import bpy, math + +#from . import cache +from .. utility import * + +def init(self, prev_container): + + #store_existing(prev_container) + + #set_settings() + + configure_world() + + configure_lights() + + configure_meshes(self) + +def configure_world(): + pass + +def configure_lights(): + pass + +def configure_meshes(self): + + for mat in bpy.data.materials: + if mat.users < 1: + bpy.data.materials.remove(mat) + + for mat in bpy.data.materials: + if mat.name.startswith("."): + if "_Original" in mat.name: + bpy.data.materials.remove(mat) + + for image in bpy.data.images: + if image.name.endswith("_baked"): + bpy.data.images.remove(image, do_unlink=True) + + iterNum = 1 + currentIterNum = 0 + + scene = bpy.context.scene + + for obj in scene.objects: + if obj.type == "MESH": + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + obj.hide_select = False #Remember to toggle this back + + currentIterNum = currentIterNum + 1 + + obj.octane.baking_group_id = 1 + currentIterNum #0 doesn't exist, 1 is neutral and 2 is first baked object + + print("Obj: " + obj.name + " set to baking group: " + str(obj.octane.baking_group_id)) + + for slot in obj.material_slots: + if "." + slot.name + '_Original' in bpy.data.materials: + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("The material: " + slot.name + " shifted to " + "." + slot.name + '_Original') + slot.material = bpy.data.materials["." + slot.name + '_Original'] + + + objWasHidden = False + + #For some reason, a Blender bug might prevent invisible objects from being smart projected + #We will turn the object temporarily visible + obj.hide_viewport = False + obj.hide_set(False) + + #Configure selection + bpy.ops.object.select_all(action='DESELECT') + bpy.context.view_layer.objects.active = obj + obj.select_set(True) + obs = bpy.context.view_layer.objects + active = obs.active + + uv_layers = obj.data.uv_layers + if not obj.TLM_ObjectProperties.tlm_use_default_channel: + uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel + else: + uv_channel = "UVMap_Lightmap" + + if not uv_channel in uv_layers: + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("UV map created for obj: " + obj.name) + uvmap = uv_layers.new(name=uv_channel) + uv_layers.active_index = len(uv_layers) - 1 + print("Setting active UV to: " + uv_layers.active_index) + + #If lightmap + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "Lightmap": + bpy.ops.uv.lightmap_pack('EXEC_SCREEN', PREF_CONTEXT='ALL_FACES', PREF_MARGIN_DIV=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin) + + #If smart project + elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "SmartProject": + + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Smart Project B") + bpy.ops.object.select_all(action='DESELECT') + obj.select_set(True) + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='SELECT') + #API changes in 2.91 causes errors: + if (2, 91, 0) > bpy.app.version: + bpy.ops.uv.smart_project(angle_limit=45.0, island_margin=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin, user_area_weight=1.0, use_aspect=True, stretch_to_bounds=False) + else: + angle = math.radians(45.0) + bpy.ops.uv.smart_project(angle_limit=angle, island_margin=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin, area_weight=1.0, correct_aspect=True, scale_to_bounds=False) + bpy.ops.mesh.select_all(action='DESELECT') + bpy.ops.object.mode_set(mode='OBJECT') + + elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "Xatlas": + + Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj) + + elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": + + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("ATLAS GROUP: " + obj.TLM_ObjectProperties.tlm_atlas_pointer) + + else: #if copy existing + + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Copied Existing UV Map for object: " + obj.name) + + else: + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Existing UV map found for obj: " + obj.name) + for i in range(0, len(uv_layers)): + if uv_layers[i].name == uv_channel: + uv_layers.active_index = i + break + + set_camera() + +def set_camera(): + + cam_name = "TLM-BakeCam" + + if not cam_name in bpy.context.scene: + camera = bpy.data.cameras.new(cam_name) + camobj_name = "TLM-BakeCam-obj" + cam_obj = bpy.data.objects.new(camobj_name, camera) + bpy.context.collection.objects.link(cam_obj) + cam_obj.location = ((0,0,0)) + + bpy.context.scene.camera = cam_obj + +def set_settings(): + + scene = bpy.context.scene + cycles = scene.cycles + scene.render.engine = "CYCLES" + sceneProperties = scene.TLM_SceneProperties + engineProperties = scene.TLM_EngineProperties + cycles.device = scene.TLM_EngineProperties.tlm_mode + + if cycles.device == "GPU": + scene.render.tile_x = 256 + scene.render.tile_y = 256 + else: + scene.render.tile_x = 32 + scene.render.tile_y = 32 + + if engineProperties.tlm_quality == "0": + cycles.samples = 32 + cycles.max_bounces = 1 + cycles.diffuse_bounces = 1 + cycles.glossy_bounces = 1 + cycles.transparent_max_bounces = 1 + cycles.transmission_bounces = 1 + cycles.volume_bounces = 1 + cycles.caustics_reflective = False + cycles.caustics_refractive = False + elif engineProperties.tlm_quality == "1": + cycles.samples = 64 + cycles.max_bounces = 2 + cycles.diffuse_bounces = 2 + cycles.glossy_bounces = 2 + cycles.transparent_max_bounces = 2 + cycles.transmission_bounces = 2 + cycles.volume_bounces = 2 + cycles.caustics_reflective = False + cycles.caustics_refractive = False + elif engineProperties.tlm_quality == "2": + cycles.samples = 512 + cycles.max_bounces = 2 + cycles.diffuse_bounces = 2 + cycles.glossy_bounces = 2 + cycles.transparent_max_bounces = 2 + cycles.transmission_bounces = 2 + cycles.volume_bounces = 2 + cycles.caustics_reflective = False + cycles.caustics_refractive = False + elif engineProperties.tlm_quality == "3": + cycles.samples = 1024 + cycles.max_bounces = 256 + cycles.diffuse_bounces = 256 + cycles.glossy_bounces = 256 + cycles.transparent_max_bounces = 256 + cycles.transmission_bounces = 256 + cycles.volume_bounces = 256 + cycles.caustics_reflective = False + cycles.caustics_refractive = False + elif engineProperties.tlm_quality == "4": + cycles.samples = 2048 + cycles.max_bounces = 512 + cycles.diffuse_bounces = 512 + cycles.glossy_bounces = 512 + cycles.transparent_max_bounces = 512 + cycles.transmission_bounces = 512 + cycles.volume_bounces = 512 + cycles.caustics_reflective = True + cycles.caustics_refractive = True + else: #Custom + pass + +def store_existing(prev_container): + + scene = bpy.context.scene + cycles = scene.cycles + + selected = [] + + for obj in bpy.context.scene.objects: + if obj.select_get(): + selected.append(obj.name) + + prev_container["settings"] = [ + cycles.samples, + cycles.max_bounces, + cycles.diffuse_bounces, + cycles.glossy_bounces, + cycles.transparent_max_bounces, + cycles.transmission_bounces, + cycles.volume_bounces, + cycles.caustics_reflective, + cycles.caustics_refractive, + cycles.device, + scene.render.engine, + bpy.context.view_layer.objects.active, + selected, + [scene.render.resolution_x, scene.render.resolution_y] + ] \ No newline at end of file diff --git a/blender/arm/lightmapper/utility/octane/lightmap2.py b/blender/arm/lightmapper/utility/octane/lightmap2.py new file mode 100644 index 00000000..ad843276 --- /dev/null +++ b/blender/arm/lightmapper/utility/octane/lightmap2.py @@ -0,0 +1,71 @@ +import bpy, os + +def bake(): + + cam_name = "TLM-BakeCam-obj" + + if cam_name in bpy.context.scene.objects: + + print("Camera found...") + + camera = bpy.context.scene.objects[cam_name] + + camera.data.octane.baking_camera = True + + for obj in bpy.context.scene.objects: + bpy.ops.object.select_all(action='DESELECT') + obj.select_set(False) + + iterNum = 2 + currentIterNum = 1 + + for obj in bpy.context.scene.objects: + if obj.type == "MESH": + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + iterNum = iterNum + 1 + + if iterNum > 1: + iterNum = iterNum - 1 + + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + + currentIterNum = currentIterNum + 1 + + scene = bpy.context.scene + + print("Baking obj: " + obj.name) + + print("Baking ID: " + str(currentIterNum) + " out of " + str(iterNum)) + + bpy.ops.object.select_all(action='DESELECT') + + camera.data.octane.baking_group_id = currentIterNum + + savedir = os.path.dirname(bpy.data.filepath) + user_dir = scene.TLM_Engine3Properties.tlm_lightmap_savedir + directory = os.path.join(savedir, user_dir) + + image_settings = bpy.context.scene.render.image_settings + image_settings.file_format = "HDR" + image_settings.color_depth = '32' + + filename = os.path.join(directory, "LM") + "_" + obj.name + ".hdr" + bpy.context.scene.render.filepath = filename + + resolution = int(obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution) + + bpy.context.scene.render.resolution_x = resolution + bpy.context.scene.render.resolution_y = resolution + + bpy.ops.render.render(write_still=True) + + else: + + print("No baking camera found") + + + + + print("Baking in Octane!") \ No newline at end of file diff --git a/blender/arm/lightmapper/utility/pack.py b/blender/arm/lightmapper/utility/pack.py index 3c2768c7..c2e1f15c 100644 --- a/blender/arm/lightmapper/utility/pack.py +++ b/blender/arm/lightmapper/utility/pack.py @@ -106,7 +106,7 @@ def postpack(): rect = [] #For each object that targets the atlas - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: if obj.TLM_ObjectProperties.tlm_postpack_object: if obj.TLM_ObjectProperties.tlm_postatlas_pointer == atlas.name: @@ -156,7 +156,13 @@ def postpack(): obj = bpy.data.objects[aob] for idx, layer in enumerate(obj.data.uv_layers): - if layer.name == "UVMap_Lightmap": + + if not obj.TLM_ObjectProperties.tlm_use_default_channel: + uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel + else: + uv_channel = "UVMap_Lightmap" + + if layer.name == uv_channel: obj.data.uv_layers.active_index = idx print("UVLayer set to: " + str(obj.data.uv_layers.active_index)) @@ -194,7 +200,7 @@ def postpack(): print("Written: " + str(os.path.join(lightmap_directory, atlas.name + end + formatEnc))) #Change the material for each material, slot - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: if obj.TLM_ObjectProperties.tlm_postpack_object: if obj.TLM_ObjectProperties.tlm_postatlas_pointer == atlas.name: @@ -219,7 +225,7 @@ def postpack(): existing_image.user_clear() #Add dilation map here... - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: if obj.TLM_ObjectProperties.tlm_postpack_object: if obj.TLM_ObjectProperties.tlm_postatlas_pointer == atlas.name: diff --git a/blender/arm/lightmapper/utility/utility.py b/blender/arm/lightmapper/utility/utility.py index 75929157..b206c773 100644 --- a/blender/arm/lightmapper/utility/utility.py +++ b/blender/arm/lightmapper/utility/utility.py @@ -1,5 +1,5 @@ import bpy.ops as O -import bpy, os, re, sys, importlib, struct, platform, subprocess, threading, string, bmesh +import bpy, os, re, sys, importlib, struct, platform, subprocess, threading, string, bmesh, shutil, glob, uuid from io import StringIO from threading import Thread from queue import Queue, Empty @@ -81,15 +81,8 @@ def save_image(image): image.filepath_raw = savepath - # if "Normal" in image.name: - # bpy.context.scene.render.image_settings.quality = 90 - # image.save_render( filepath = image.filepath_raw, scene = bpy.context.scene ) - # else: image.save() - - - def get_file_size(filepath): size = "Unpack Files" try: @@ -141,7 +134,7 @@ def check_is_org_material(self,material): def clean_empty_materials(self): - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: for slot in obj.material_slots: mat = slot.material if mat is None: @@ -319,6 +312,11 @@ def lightmap_to_ao(material,lightmap_node): # https://github.com/mattedicksoncom/blender-xatlas/ ########################################################### +def gen_safe_name(): + genId = uuid.uuid4().hex + # genId = "u_" + genId.replace("-","_") + return "u_" + genId + def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj): blender_xatlas = importlib.util.find_spec("blender_xatlas") @@ -330,32 +328,54 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj): packOptions = bpy.context.scene.pack_tool chartOptions = bpy.context.scene.chart_tool + sharedProperties = bpy.context.scene.shared_properties + #sharedProperties.unwrapSelection context = bpy.context - - if obj.type == 'MESH': - context.view_layer.objects.active = obj - if obj.data.users > 1: - obj.data = obj.data.copy() #make single user copy - uv_layers = obj.data.uv_layers - #setup the lightmap uvs - uvName = "UVMap_Lightmap" - if sharedProperties.lightmapUVChoiceType == "NAME": - uvName = sharedProperties.lightmapUVName - elif sharedProperties.lightmapUVChoiceType == "INDEX": - if sharedProperties.lightmapUVIndex < len(uv_layers): - uvName = uv_layers[sharedProperties.lightmapUVIndex].name + #save whatever mode the user was in + startingMode = bpy.context.object.mode + selected_objects = bpy.context.selected_objects - if not uvName in uv_layers: - uvmap = uv_layers.new(name=uvName) - uv_layers.active_index = len(uv_layers) - 1 - else: - for i in range(0, len(uv_layers)): - if uv_layers[i].name == uvName: - uv_layers.active_index = i - obj.select_set(True) + #check something is actually selected + #external function/operator will select them + if len(selected_objects) == 0: + print("Nothing Selected") + self.report({"WARNING"}, "Nothing Selected, please select Something") + return {'FINISHED'} + + #store the names of objects to be lightmapped + rename_dict = dict() + safe_dict = dict() + + #make sure all the objects have ligthmap uvs + for obj in selected_objects: + if obj.type == 'MESH': + safe_name = gen_safe_name(); + rename_dict[obj.name] = (obj.name,safe_name) + safe_dict[safe_name] = obj.name + context.view_layer.objects.active = obj + if obj.data.users > 1: + obj.data = obj.data.copy() #make single user copy + uv_layers = obj.data.uv_layers + + #setup the lightmap uvs + uvName = "UVMap_Lightmap" + if sharedProperties.lightmapUVChoiceType == "NAME": + uvName = sharedProperties.lightmapUVName + elif sharedProperties.lightmapUVChoiceType == "INDEX": + if sharedProperties.lightmapUVIndex < len(uv_layers): + uvName = uv_layers[sharedProperties.lightmapUVIndex].name + + if not uvName in uv_layers: + uvmap = uv_layers.new(name=uvName) + uv_layers.active_index = len(uv_layers) - 1 + else: + for i in range(0, len(uv_layers)): + if uv_layers[i].name == uvName: + uv_layers.active_index = i + obj.select_set(True) #save all the current edges if sharedProperties.packOnly: @@ -381,8 +401,11 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj): bpy.ops.object.mode_set(mode='OBJECT') + #Create a fake obj export to a string + #Will strip this down further later fakeFile = StringIO() blender_xatlas.export_obj_simple.save( + rename_dict=rename_dict, context=bpy.context, filepath=fakeFile, mainUVChoiceType=sharedProperties.mainUVChoiceType, @@ -393,20 +416,26 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj): use_mesh_modifiers=True, use_edges=True, use_smooth_groups=False, - use_smooth_groups_bitflags=False, + use_smooth_groups_bitflags=False, use_normals=True, use_uvs=True, use_materials=False, use_triangles=False, - use_nurbs=False, - use_vertex_groups=False, + use_nurbs=False, + use_vertex_groups=False, use_blen_objects=True, group_by_object=False, group_by_material=False, keep_vertex_order=False, ) - file_path = os.path.dirname(os.path.abspath(blender_xatlas.__file__)) + #print just for reference + # print(fakeFile.getvalue()) + + #get the path to xatlas + #file_path = os.path.dirname(os.path.abspath(__file__)) + scriptsDir = bpy.utils.user_resource('SCRIPTS', "addons") + file_path = os.path.join(scriptsDir, "blender_xatlas") if platform.system() == "Windows": xatlas_path = os.path.join(file_path, "xatlas", "xatlas-blender.exe") elif platform.system() == "Linux": @@ -458,6 +487,8 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj): shell=True ) + print(xatlas_path) + #shove the fake file in stdin stdin = xatlas_process.stdin value = bytes(fakeFile.getvalue() + "\n", 'UTF-8') #The \n is needed to end the input properly @@ -482,17 +513,17 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj): obName: string = "" uvArray: List[float] = field(default_factory=list) faceArray: List[int] = field(default_factory=list) - + convertedObjects = [] uvArrayComplete = [] - + #search through the out put for STARTOBJ #then start reading the objects obTest = None startRead = False for line in outObj.splitlines(): - + line_split = line.split() if not line_split: @@ -504,14 +535,14 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj): print("Start reading the objects----------------------------------------") startRead = True # obTest = uvObject() - + if startRead: #if it's a new obj if line_start == 'o': #if there is already an object append it if obTest is not None: convertedObjects.append(obTest) - + obTest = uvObject() #create new uv object obTest.obName = line_split[1] @@ -536,9 +567,9 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj): #append the final object convertedObjects.append(obTest) - # print(convertedObjects) - - + print(convertedObjects) + + #apply the output------------------------------------------------------------- #copy the uvs to the original objects # objIndex = 0 @@ -548,7 +579,7 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj): bpy.ops.object.select_all(action='DESELECT') obTest = importObject - + obTest.obName = safe_dict[obTest.obName] #probably shouldn't just replace it bpy.context.scene.objects[obTest.obName].select_set(True) context.view_layer.objects.active = bpy.context.scene.objects[obTest.obName] bpy.ops.object.mode_set(mode = 'OBJECT') @@ -563,7 +594,7 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj): nFaces = len(bm.faces) #need to ensure lookup table for some reason? - if hasattr(bm.faces, "ensure_lookup_table"): + if hasattr(bm.faces, "ensure_lookup_table"): bm.faces.ensure_lookup_table() #loop through the faces @@ -601,7 +632,7 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj): currentObject = bpy.context.scene.objects[edgeList['object']] bm = bmesh.new() bm.from_mesh(currentObject.data) - if hasattr(bm.edges, "ensure_lookup_table"): + if hasattr(bm.edges, "ensure_lookup_table"): bm.edges.ensure_lookup_table() #assume that all the triangulated edges come after the original edges @@ -617,6 +648,27 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj): bm.free() bpy.ops.object.mode_set(mode='EDIT') - #End setting the quads back again------------------------------------------------------------ + #End setting the quads back again------------------------------------------------------------- - print("Finished Xatlas----------------------------------------") \ No newline at end of file + #select the original objects that were selected + for objectName in rename_dict: + if objectName[0] in bpy.context.scene.objects: + current_object = bpy.context.scene.objects[objectName[0]] + current_object.select_set(True) + context.view_layer.objects.active = current_object + + bpy.ops.object.mode_set(mode=startingMode) + + print("Finished Xatlas----------------------------------------") + return {'FINISHED'} + +def transfer_assets(copy, source, destination): + for filename in glob.glob(os.path.join(source, '*.*')): + shutil.copy(filename, destination) + +def transfer_load(): + load_folder = bpy.path.abspath(os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_SceneProperties.tlm_load_folder)) + lightmap_folder = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir) + print(load_folder) + print(lightmap_folder) + transfer_assets(True, load_folder, lightmap_folder) \ No newline at end of file diff --git a/blender/arm/props_bake.py b/blender/arm/props_bake.py index 25bff93a..e433e682 100644 --- a/blender/arm/props_bake.py +++ b/blender/arm/props_bake.py @@ -3,7 +3,7 @@ import arm.assets import bpy from bpy.types import Menu, Panel, UIList from bpy.props import * -from arm.lightmapper import operators, properties, utility, keymap +from arm.lightmapper import operators, properties, utility class ArmBakeListItem(bpy.types.PropertyGroup): obj: PointerProperty(type=bpy.types.Object, description="The object to bake") @@ -361,7 +361,6 @@ def register(): operators.register() properties.register() - keymap.register() def unregister(): bpy.utils.unregister_class(ArmBakeListItem) @@ -380,5 +379,4 @@ def unregister(): #Unregister lightmapper operators.unregister() - properties.unregister() - keymap.unregister() \ No newline at end of file + properties.unregister() \ No newline at end of file diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index 32f3c719..d8531192 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -20,6 +20,7 @@ import arm.utils from arm.lightmapper.utility import icon from arm.lightmapper.properties.denoiser import oidn, optix +from arm.lightmapper.panels import scene import importlib @@ -73,9 +74,18 @@ class ARM_PT_ObjectPropsPanel(bpy.types.Panel): if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: row = layout.row() - row.prop(obj.TLM_ObjectProperties, "tlm_mesh_lightmap_resolution") + 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_unwrap_mode") + 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": @@ -93,7 +103,6 @@ class ARM_PT_ObjectPropsPanel(bpy.types.Panel): 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() @@ -1482,423 +1491,6 @@ class ARM_PT_BakePanel(bpy.types.Panel): layout.prop(item, "res_x") layout.prop(item, "res_y") - else: - - scene = context.scene - sceneProperties = scene.TLM_SceneProperties - row = layout.row(align=True) - - row = layout.row(align=True) - - #We list LuxCoreRender as available, by default we assume Cycles exists - row.prop(sceneProperties, "tlm_lightmap_engine") - - if sceneProperties.tlm_lightmap_engine == "Cycles": - - #CYCLES SETTINGS HERE - engineProperties = scene.TLM_EngineProperties - - row = layout.row(align=True) - row.label(text="General Settings") - row = layout.row(align=True) - row.operator("tlm.build_lightmaps") - row = layout.row(align=True) - row.operator("tlm.clean_lightmaps") - row = layout.row(align=True) - row.operator("tlm.explore_lightmaps") - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_apply_on_unwrap") - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_headless") - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_alert_on_finish") - - if sceneProperties.tlm_alert_on_finish: - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_alert_sound") - - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_verbose") - #row = layout.row(align=True) - #row.prop(sceneProperties, "tlm_compile_statistics") - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_override_bg_color") - if sceneProperties.tlm_override_bg_color: - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_override_color") - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_reset_uv") - - row = layout.row(align=True) - try: - if bpy.context.scene["TLM_Buildstat"] is not None: - row.label(text="Last build completed in: " + str(bpy.context.scene["TLM_Buildstat"][0])) - except: - pass - - row = layout.row(align=True) - row.label(text="Cycles Settings") - - row = layout.row(align=True) - row.prop(engineProperties, "tlm_mode") - row = layout.row(align=True) - row.prop(engineProperties, "tlm_quality") - row = layout.row(align=True) - row.prop(engineProperties, "tlm_resolution_scale") - row = layout.row(align=True) - row.prop(engineProperties, "tlm_bake_mode") - row = layout.row(align=True) - row.prop(engineProperties, "tlm_lighting_mode") - - if scene.TLM_EngineProperties.tlm_bake_mode == "Background": - row = layout.row(align=True) - row.label(text="Warning! Background mode is currently unstable", icon_value=2) - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_network_render") - if sceneProperties.tlm_network_render: - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_network_paths") - #row = layout.row(align=True) - #row.prop(sceneProperties, "tlm_network_dir") - row = layout.row(align=True) - row = layout.row(align=True) - row.prop(engineProperties, "tlm_caching_mode") - row = layout.row(align=True) - row.prop(engineProperties, "tlm_directional_mode") - row = layout.row(align=True) - row.prop(engineProperties, "tlm_lightmap_savedir") - row = layout.row(align=True) - row.prop(engineProperties, "tlm_dilation_margin") - row = layout.row(align=True) - row.prop(engineProperties, "tlm_exposure_multiplier") - row = layout.row(align=True) - row.prop(engineProperties, "tlm_setting_supersample") - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_metallic_clamp") - - elif sceneProperties.tlm_lightmap_engine == "LuxCoreRender": - - #LUXCORE SETTINGS HERE - luxcore_available = False - - #Look for Luxcorerender in the renderengine classes - for engine in bpy.types.RenderEngine.__subclasses__(): - if engine.bl_idname == "LUXCORE": - luxcore_available = True - break - - row = layout.row(align=True) - if not luxcore_available: - row.label(text="Please install BlendLuxCore.") - else: - row.label(text="LuxCoreRender not yet available.") - - elif sceneProperties.tlm_lightmap_engine == "OctaneRender": - - #LUXCORE SETTINGS HERE - octane_available = False - - row = layout.row(align=True) - row.label(text="Octane Render not yet available.") - - - ################## - #DENOISE SETTINGS! - row = layout.row(align=True) - row.label(text="Denoise Settings") - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_denoise_use") - row = layout.row(align=True) - - if sceneProperties.tlm_denoise_use: - row.prop(sceneProperties, "tlm_denoise_engine", expand=True) - row = layout.row(align=True) - - if sceneProperties.tlm_denoise_engine == "Integrated": - row.label(text="No options for Integrated.") - elif sceneProperties.tlm_denoise_engine == "OIDN": - denoiseProperties = scene.TLM_OIDNEngineProperties - row.prop(denoiseProperties, "tlm_oidn_path") - row = layout.row(align=True) - row.prop(denoiseProperties, "tlm_oidn_verbose") - row = layout.row(align=True) - row.prop(denoiseProperties, "tlm_oidn_threads") - row = layout.row(align=True) - row.prop(denoiseProperties, "tlm_oidn_maxmem") - row = layout.row(align=True) - row.prop(denoiseProperties, "tlm_oidn_affinity") - # row = layout.row(align=True) - # row.prop(denoiseProperties, "tlm_denoise_ao") - elif sceneProperties.tlm_denoise_engine == "Optix": - denoiseProperties = scene.TLM_OptixEngineProperties - row.prop(denoiseProperties, "tlm_optix_path") - row = layout.row(align=True) - row.prop(denoiseProperties, "tlm_optix_verbose") - row = layout.row(align=True) - row.prop(denoiseProperties, "tlm_optix_maxmem") - - - ################## - #FILTERING SETTINGS! - row = layout.row(align=True) - row.label(text="Filtering Settings") - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_filtering_use") - row = layout.row(align=True) - - if sceneProperties.tlm_filtering_use: - - if sceneProperties.tlm_filtering_engine == "OpenCV": - - cv2 = importlib.util.find_spec("cv2") - - if cv2 is None: - row = layout.row(align=True) - row.label(text="OpenCV is not installed. Install it below.") - row = layout.row(align=True) - row.label(text="It is recommended to install as administrator.") - row = layout.row(align=True) - row.operator("tlm.install_opencv_lightmaps") - else: - row = layout.row(align=True) - row.prop(scene.TLM_SceneProperties, "tlm_filtering_mode") - row = layout.row(align=True) - if scene.TLM_SceneProperties.tlm_filtering_mode == "Gaussian": - row.prop(scene.TLM_SceneProperties, "tlm_filtering_gaussian_strength") - row = layout.row(align=True) - row.prop(scene.TLM_SceneProperties, "tlm_filtering_iterations") - elif scene.TLM_SceneProperties.tlm_filtering_mode == "Box": - row.prop(scene.TLM_SceneProperties, "tlm_filtering_box_strength") - row = layout.row(align=True) - row.prop(scene.TLM_SceneProperties, "tlm_filtering_iterations") - - elif scene.TLM_SceneProperties.tlm_filtering_mode == "Bilateral": - row.prop(scene.TLM_SceneProperties, "tlm_filtering_bilateral_diameter") - row = layout.row(align=True) - row.prop(scene.TLM_SceneProperties, "tlm_filtering_bilateral_color_deviation") - row = layout.row(align=True) - row.prop(scene.TLM_SceneProperties, "tlm_filtering_bilateral_coordinate_deviation") - row = layout.row(align=True) - row.prop(scene.TLM_SceneProperties, "tlm_filtering_iterations") - else: - row.prop(scene.TLM_SceneProperties, "tlm_filtering_median_kernel", expand=True) - row = layout.row(align=True) - row.prop(scene.TLM_SceneProperties, "tlm_filtering_iterations") - else: - row = layout.row(align=True) - row.prop(scene.TLM_SceneProperties, "tlm_numpy_filtering_mode") - - - ################## - #ENCODING SETTINGS! - row = layout.row(align=True) - row.label(text="Encoding Settings") - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_encoding_use") - row = layout.row(align=True) - - if sceneProperties.tlm_encoding_use: - - - if scene.TLM_EngineProperties.tlm_bake_mode == "Background": - row.label(text="Encoding options disabled in background mode") - row = layout.row(align=True) - - row.prop(sceneProperties, "tlm_encoding_device", expand=True) - row = layout.row(align=True) - - if sceneProperties.tlm_encoding_device == "CPU": - row.prop(sceneProperties, "tlm_encoding_mode_a", expand=True) - else: - row.prop(sceneProperties, "tlm_encoding_mode_b", expand=True) - - if sceneProperties.tlm_encoding_device == "CPU": - if sceneProperties.tlm_encoding_mode_a == "RGBM": - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_encoding_range") - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_decoder_setup") - if sceneProperties.tlm_encoding_mode_a == "RGBD": - pass - if sceneProperties.tlm_encoding_mode_a == "HDR": - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_format") - else: - - if sceneProperties.tlm_encoding_mode_b == "RGBM": - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_encoding_range") - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_decoder_setup") - - if sceneProperties.tlm_encoding_mode_b == "LogLuv" and sceneProperties.tlm_encoding_device == "GPU": - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_decoder_setup") - if sceneProperties.tlm_encoding_mode_b == "HDR": - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_format") - - ################## - #SELECTION OPERATORS! - - row = layout.row(align=True) - row.label(text="Selection Operators") - row = layout.row(align=True) - - row = layout.row(align=True) - row.operator("tlm.enable_selection") - row = layout.row(align=True) - row.operator("tlm.disable_selection") - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_override_object_settings") - - if sceneProperties.tlm_override_object_settings: - - row = layout.row(align=True) - row = layout.row() - row.prop(sceneProperties, "tlm_mesh_lightmap_unwrap_mode") - row = layout.row() - - if sceneProperties.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(sceneProperties, "tlm_atlas_pointer", scene, "TLM_AtlasList", text='Atlas Group') - else: - row = layout.label(text="Add Atlas Groups from the scene lightmapping settings.") - - else: - row = layout.row() - row.prop(sceneProperties, "tlm_postpack_object") - row = layout.row() - - if sceneProperties.tlm_postpack_object and sceneProperties.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(sceneProperties, "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() - - if sceneProperties.tlm_mesh_lightmap_unwrap_mode != "AtlasGroupA": - row.prop(sceneProperties, "tlm_mesh_lightmap_resolution") - row = layout.row() - row.prop(sceneProperties, "tlm_mesh_unwrap_margin") - - row = layout.row(align=True) - row.operator("tlm.remove_uv_selection") - row = layout.row(align=True) - row.operator("tlm.select_lightmapped_objects") - row = layout.row(align=True) - - ################## - #Additional settings - row = layout.row(align=True) - row.label(text="Additional options") - sceneProperties = scene.TLM_SceneProperties - atlasListItem = scene.TLM_AtlasListItem - atlasList = scene.TLM_AtlasList - postatlasListItem = scene.TLM_PostAtlasListItem - postatlasList = scene.TLM_PostAtlasList - - layout.label(text="Atlas Groups") - row = layout.row() - row.prop(sceneProperties, "tlm_atlas_mode", expand=True) - - if sceneProperties.tlm_atlas_mode == "Prepack": - - rows = 2 - if len(atlasList) > 1: - rows = 4 - row = layout.row() - row.template_list("TLM_UL_AtlasList", "Atlas List", scene, "TLM_AtlasList", scene, "TLM_AtlasListItem", rows=rows) - col = row.column(align=True) - col.operator("tlm_atlaslist.new_item", icon='ADD', text="") - col.operator("tlm_atlaslist.delete_item", icon='REMOVE', text="") - #col.menu("ARM_MT_BakeListSpecials", icon='DOWNARROW_HLT', text="") - - # if len(scene.TLM_AtlasList) > 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 atlasListItem >= 0 and len(atlasList) > 0: - item = atlasList[atlasListItem] - #layout.prop_search(item, "obj", bpy.data, "objects", text="Object") - #layout.prop(item, "res_x") - layout.prop(item, "tlm_atlas_lightmap_unwrap_mode") - layout.prop(item, "tlm_atlas_lightmap_resolution") - layout.prop(item, "tlm_atlas_unwrap_margin") - - amount = 0 - - for obj in bpy.data.objects: - if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: - if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": - if obj.TLM_ObjectProperties.tlm_atlas_pointer == item.name: - amount = amount + 1 - - layout.label(text="Objects: " + str(amount)) - - # layout.use_property_split = True - # layout.use_property_decorate = False - # layout.label(text="Enable for selection") - # layout.label(text="Disable for selection") - # layout.label(text="Something...") - - else: - - layout.label(text="Postpacking is unstable.") - rows = 2 - if len(atlasList) > 1: - rows = 4 - row = layout.row() - row.template_list("TLM_UL_PostAtlasList", "PostList", scene, "TLM_PostAtlasList", scene, "TLM_PostAtlasListItem", rows=rows) - col = row.column(align=True) - col.operator("tlm_postatlaslist.new_item", icon='ADD', text="") - col.operator("tlm_postatlaslist.delete_item", icon='REMOVE', text="") - - if postatlasListItem >= 0 and len(postatlasList) > 0: - item = postatlasList[postatlasListItem] - layout.prop(item, "tlm_atlas_lightmap_resolution") - - #Below list object counter - amount = 0 - utilized = 0 - atlasUsedArea = 0 - atlasSize = item.tlm_atlas_lightmap_resolution - - for obj in bpy.data.objects: - if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: - if obj.TLM_ObjectProperties.tlm_postpack_object: - if obj.TLM_ObjectProperties.tlm_postatlas_pointer == item.name: - amount = amount + 1 - - atlasUsedArea += int(obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution) ** 2 - - row = layout.row() - row.prop(item, "tlm_atlas_repack_on_cleanup") - - #TODO SET A CHECK FOR THIS! ADD A CV2 CHECK TO UTILITY! - cv2 = True - - if cv2: - row = layout.row() - row.prop(item, "tlm_atlas_dilation") - layout.label(text="Objects: " + str(amount)) - - utilized = atlasUsedArea / (int(atlasSize) ** 2) - layout.label(text="Utilized: " + str(utilized * 100) + "%") - - if (utilized * 100) > 100: - layout.label(text="Warning! Overflow not yet supported") - class ArmGenLodButton(bpy.types.Operator): """Automatically generate LoD levels.""" bl_idname = 'arm.generate_lod' @@ -2671,6 +2263,13 @@ def register(): 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) @@ -2743,3 +2342,10 @@ def unregister(): 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) \ No newline at end of file From 5fe816d16a35fec61ee866c04df36fb20d348a9a Mon Sep 17 00:00:00 2001 From: Alexander Kleemann Date: Thu, 18 Mar 2021 19:25:02 +0100 Subject: [PATCH 067/264] Add in-menu option for OpenCV installation --- blender/arm/lightmapper/panels/scene.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/blender/arm/lightmapper/panels/scene.py b/blender/arm/lightmapper/panels/scene.py index 56c05cb7..de36f22f 100644 --- a/blender/arm/lightmapper/panels/scene.py +++ b/blender/arm/lightmapper/panels/scene.py @@ -259,7 +259,9 @@ class TLM_PT_Filtering(bpy.types.Panel): if cv2 is None: row = layout.row(align=True) - row.label(text="OpenCV is not installed. Install it through preferences.") + row.label(text="OpenCV is not installed. Please install it as an administrator.") + row = layout.row(align=True) + row.operator("tlm.install_opencv_lightmaps") else: row = layout.row(align=True) row.prop(scene.TLM_SceneProperties, "tlm_filtering_mode") From 255921b9eeaaf45fb893cb860de5108d5fee01e5 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC <55564981+QuantumCoderQC@users.noreply.github.com> Date: Thu, 18 Mar 2021 21:34:59 +0100 Subject: [PATCH 068/264] Make description of logic node more precise The OnVolumeTrigger node does not required a RigidBody. --- blender/arm/logicnode/physics/LN_on_volume_trigger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/logicnode/physics/LN_on_volume_trigger.py b/blender/arm/logicnode/physics/LN_on_volume_trigger.py index 7b796d84..225aa301 100644 --- a/blender/arm/logicnode/physics/LN_on_volume_trigger.py +++ b/blender/arm/logicnode/physics/LN_on_volume_trigger.py @@ -1,7 +1,7 @@ from arm.logicnode.arm_nodes import * class OnVolumeTriggerNode(ArmLogicTreeNode): - """Activates the output when the given rigid body enter, overlap or leave the given trigger. + """Activates the output when the given object enters, overlaps or leaves the bounding box of the given trigger object. (Note: Works even if objects are not Rigid Bodies). @input RB: this object is taken as the entering object @input Trigger: this object is used as the volume trigger From a778460d283e5178984e224edf08cb879cf37616 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Thu, 18 Mar 2021 22:49:56 -0300 Subject: [PATCH 069/264] Expose the exported rigid body type to the UI to make rb less confusing --- blender/arm/exporter.py | 6 +++++- blender/arm/props_ui.py | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/blender/arm/exporter.py b/blender/arm/exporter.py index 30154525..19e87a6e 100755 --- a/blender/arm/exporter.py +++ b/blender/arm/exporter.py @@ -2308,6 +2308,10 @@ class ArmoryExporter: return instanced_type, instanced_data + @staticmethod + def rigid_body_static(rb): + return (not rb.enabled and not rb.kinematic) or (rb.type == 'PASSIVE' and not rb.kinematic) + def post_export_object(self, bobject: bpy.types.Object, o, type): # Export traits self.export_traits(bobject, o) @@ -2334,7 +2338,7 @@ class ArmoryExporter: elif rb.collision_shape == 'CAPSULE': shape = 6 body_mass = rb.mass - is_static = (not rb.enabled and not rb.kinematic) or (rb.type == 'PASSIVE' and not rb.kinematic) + is_static = self.rigid_body_static(rb) if is_static: body_mass = 0 x = {} diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index 582ee734..2c6d3ec5 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -22,6 +22,8 @@ from arm.lightmapper.utility import icon from arm.lightmapper.properties.denoiser import oidn, optix import importlib +from arm.exporter import ArmoryExporter + # Menu in object region class ARM_PT_ObjectPropsPanel(bpy.types.Panel): bl_label = "Armory Props" @@ -180,7 +182,19 @@ class ARM_PT_PhysicsPropsPanel(bpy.types.Panel): if obj == None: return - if obj.rigid_body != None: + 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') From d4463a361153a573602dec3eef6a279fa96a280d Mon Sep 17 00:00:00 2001 From: N8n5h Date: Fri, 19 Mar 2021 10:22:01 -0300 Subject: [PATCH 070/264] Catch tangent n-gon error to properly show it to the user Instead of just letting it crash whenever the users hits play. --- blender/arm/exporter.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/blender/arm/exporter.py b/blender/arm/exporter.py index 30154525..50c525ae 100755 --- a/blender/arm/exporter.py +++ b/blender/arm/exporter.py @@ -32,6 +32,7 @@ import arm.material.mat_batch as mat_batch import arm.utils import arm.profiler +import arm.log as log @unique class NodeType(Enum): @@ -1157,7 +1158,16 @@ class ArmoryExporter: else: invscale_tex = 1 * 32767 if has_tang: - exportMesh.calc_tangents(uvmap=lay0.name) + try: + exportMesh.calc_tangents(uvmap=lay0.name) + except Exception as e: + if hasattr(e, 'message'): + log.error(e.message) + else: + # Assume it was caused because of encountering n-gons + log.error(f"""object {bobject.name} contains n-gons in its mesh, so it's impossible to compute tanget space for normal mapping. +Make sure the mesh only has tris/quads.""") + tangdata = np.empty(num_verts * 3, dtype=' Date: Fri, 19 Mar 2021 11:39:06 -0300 Subject: [PATCH 071/264] Log errors and show them in the blender UI --- blender/arm/log.py | 3 +++ blender/arm/props_ui.py | 14 +++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/blender/arm/log.py b/blender/arm/log.py index f28a1524..801555c3 100644 --- a/blender/arm/log.py +++ b/blender/arm/log.py @@ -29,6 +29,7 @@ else: info_text = '' num_warnings = 0 +num_errors = 0 def clear(clear_warnings=False): global info_text, num_warnings @@ -62,4 +63,6 @@ def warn(text): print_warn(text) def error(text): + global num_errors + num_errors += 1 log('ERROR: ' + text, ERROR) diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index 582ee734..955aa4fa 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -560,7 +560,19 @@ class ARM_PT_ArmoryPlayerPanel(bpy.types.Panel): box = layout.box() # Less spacing between lines col = box.column(align=True) - col.label(text=f'{log.num_warnings} warnings occurred during compilation!', icon='ERROR') + 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() + # 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') From 7851abc4911e80396548ad70c82fe0736ef41b81 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Sat, 20 Mar 2021 21:26:05 -0300 Subject: [PATCH 072/264] Clear error count after building again --- blender/arm/log.py | 6 ++++-- blender/arm/make.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/blender/arm/log.py b/blender/arm/log.py index 801555c3..2dca68da 100644 --- a/blender/arm/log.py +++ b/blender/arm/log.py @@ -31,11 +31,13 @@ info_text = '' num_warnings = 0 num_errors = 0 -def clear(clear_warnings=False): - global info_text, num_warnings +def clear(clear_warnings=False, clear_errors=False): + global info_text, num_warnings, num_errors info_text = '' if clear_warnings: num_warnings = 0 + if clear_errors: + num_errors = 0 def format_text(text): return (text[:80] + '..') if len(text) > 80 else text # Limit str size diff --git a/blender/arm/make.py b/blender/arm/make.py index 4538a9e6..ffc91e3a 100755 --- a/blender/arm/make.py +++ b/blender/arm/make.py @@ -338,7 +338,7 @@ def build(target, is_play=False, is_publish=False, is_export=False): if arm.utils.get_save_on_build(): bpy.ops.wm.save_mainfile() - log.clear(clear_warnings=True) + log.clear(clear_warnings=True, clear_errors=True) # Set camera in active scene active_scene = arm.utils.get_active_scene() From eb6d23fab353aecfa923cd8218736092edd5a28e Mon Sep 17 00:00:00 2001 From: Alexander Kleemann Date: Mon, 22 Mar 2021 20:26:01 +0100 Subject: [PATCH 073/264] Fix material translucency in Blender 2.9+ The extra input node in the principled is now taken into consideration --- blender/arm/material/mat_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/blender/arm/material/mat_utils.py b/blender/arm/material/mat_utils.py index 9d6eb1f0..b1567a72 100644 --- a/blender/arm/material/mat_utils.py +++ b/blender/arm/material/mat_utils.py @@ -73,7 +73,8 @@ def is_transluc_type(node): node.type == 'BSDF_TRANSPARENT' or \ node.type == 'BSDF_TRANSLUCENT' or \ (node.type == 'GROUP' and node.node_tree.name.startswith('Armory PBR') and (node.inputs[1].is_linked or node.inputs[1].default_value != 1.0)) or \ - (node.type == 'BSDF_PRINCIPLED' and len(node.inputs) > 20 and (node.inputs[18].is_linked or node.inputs[18].default_value != 1.0)): + (node.type == 'BSDF_PRINCIPLED' and len(node.inputs) > 20 and (node.inputs[18].is_linked or node.inputs[18].default_value != 1.0)) or \ + (node.type == 'BSDF_PRINCIPLED' and len(node.inputs) > 21 and (node.inputs[19].is_linked or node.inputs[19].default_value != 1.0)): return True return False From 362fffa408582fb4775f156c320811f9f4843213 Mon Sep 17 00:00:00 2001 From: Alexander Kleemann Date: Tue, 23 Mar 2021 21:51:28 +0100 Subject: [PATCH 074/264] Reimplement blackbody node from python to GLSL Should now work with changing values --- blender/arm/material/cycles_functions.py | 71 +++++++++++++++++++ .../material/cycles_nodes/nodes_converter.py | 68 ++---------------- 2 files changed, 75 insertions(+), 64 deletions(-) diff --git a/blender/arm/material/cycles_functions.py b/blender/arm/material/cycles_functions.py index a133d2f8..8135371b 100644 --- a/blender/arm/material/cycles_functions.py +++ b/blender/arm/material/cycles_functions.py @@ -332,3 +332,74 @@ vec3 wrap(const vec3 value, const vec3 max, const vec3 min) { \t wrap(value.z, max.z, min.z)); } """ + +str_blackbody = """ +vec3 blackbody(const float temperature){ + + vec3 rgb = vec3(0.0, 0.0, 0.0); + + vec3 r = vec3(0.0, 0.0, 0.0); + vec3 g = vec3(0.0, 0.0, 0.0); + vec3 b = vec3(0.0, 0.0, 0.0); + + float t_inv = float(1.0 / temperature); + + if (temperature >= 12000.0) { + + rgb = vec3(0.826270103, 0.994478524, 1.56626022); + + } else if(temperature < 965.0) { + + rgb = vec3(4.70366907, 0.0, 0.0); + + } else { + + if (temperature >= 6365.0) { + vec3 r = vec3(3.78765709e+03, 9.36026367e-06, 3.98995841e-01); + vec3 g = vec3(-5.00279505e+02, -4.59745390e-06, 1.09090465e+00); + vec4 b = vec4(6.72595954e-13, -2.73059993e-08, 4.24068546e-04, -7.52204323e-01); + + rgb = vec3(r.r * t_inv + r.g * temperature + r.b, g.r * t_inv + g.g * temperature + g.b, ((b.r * temperature + b.g) * temperature + b.b) * temperature + b.a ); + + } else if (temperature >= 3315.0) { + vec3 r = vec3(4.60124770e+03, 2.89727618e-05, 1.48001316e-01); + vec3 g = vec3(-1.18134453e+03, -2.18913373e-05, 1.30656109e+00); + vec4 b = vec4(-2.22463426e-13, -1.55078698e-08, 3.81675160e-04, -7.30646033e-01); + + rgb = vec3(r.r * t_inv + r.g * temperature + r.b, g.r * t_inv + g.g * temperature + g.b, ((b.r * temperature + b.g) * temperature + b.b) * temperature + b.a ); + + } else if (temperature >= 1902.0) { + vec3 r = vec3(4.66849800e+03, 2.85655028e-05, 1.29075375e-01); + vec3 g = vec3(-1.42546105e+03, -4.01730887e-05, 1.44002695e+00); + vec4 b = vec4(-2.02524603e-11, 1.79435860e-07, -2.60561875e-04, -1.41761141e-02); + + rgb = vec3(r.r * t_inv + r.g * temperature + r.b, g.r * t_inv + g.g * temperature + g.b, ((b.r * temperature + b.g) * temperature + b.b) * temperature + b.a ); + + } else if (temperature >= 1449.0) { + vec3 r = vec3(4.10671449e+03, -8.61949938e-05, 6.41423749e-01); + vec3 g = vec3(-1.22075471e+03, 2.56245413e-05, 1.20753416e+00); + vec4 b = vec4(0.0, 0.0, 0.0, 0.0); + + rgb = vec3(r.r * t_inv + r.g * temperature + r.b, g.r * t_inv + g.g * temperature + g.b, ((b.r * temperature + b.g) * temperature + b.b) * temperature + b.a ); + + } else if (temperature >= 1167.0) { + vec3 r = vec3(3.37763626e+03, -4.34581697e-04, 1.64843306e+00); + vec3 g = vec3(-1.00402363e+03, 1.29189794e-04, 9.08181524e-01); + vec4 b = vec4(0.0, 0.0, 0.0, 0.0); + + rgb = vec3(r.r * t_inv + r.g * temperature + r.b, g.r * t_inv + g.g * temperature + g.b, ((b.r * temperature + b.g) * temperature + b.b) * temperature + b.a ); + + } else { + vec3 r = vec3(2.52432244e+03, -1.06185848e-03, 3.11067539e+00); + vec3 g = vec3(-7.50343014e+02, 3.15679613e-04, 4.73464526e-01); + vec4 b = vec4(0.0, 0.0, 0.0, 0.0); + + rgb = vec3(r.r * t_inv + r.g * temperature + r.b, g.r * t_inv + g.g * temperature + g.b, ((b.r * temperature + b.g) * temperature + b.b) * temperature + b.a ); + + } + } + + return rgb; + +} +""" \ No newline at end of file diff --git a/blender/arm/material/cycles_nodes/nodes_converter.py b/blender/arm/material/cycles_nodes/nodes_converter.py index b87e9f16..a70c6559 100644 --- a/blender/arm/material/cycles_nodes/nodes_converter.py +++ b/blender/arm/material/cycles_nodes/nodes_converter.py @@ -10,71 +10,11 @@ from arm.material.shader import floatstr, vec3str def parse_blackbody(node: bpy.types.ShaderNodeBlackbody, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: - t = float(c.parse_value_input(node.inputs[0])) - - rgb = [0, 0, 0] - blackbody_table_r = [ - [2.52432244e+03, -1.06185848e-03, 3.11067539e+00], - [3.37763626e+03, -4.34581697e-04, 1.64843306e+00], - [4.10671449e+03, -8.61949938e-05, 6.41423749e-01], - [4.66849800e+03, 2.85655028e-05, 1.29075375e-01], - [4.60124770e+03, 2.89727618e-05, 1.48001316e-01], - [3.78765709e+03, 9.36026367e-06, 3.98995841e-01] - ] - blackbody_table_g = [ - [-7.50343014e+02, 3.15679613e-04, 4.73464526e-01], - [-1.00402363e+03, 1.29189794e-04, 9.08181524e-01], - [-1.22075471e+03, 2.56245413e-05, 1.20753416e+00], - [-1.42546105e+03, -4.01730887e-05, 1.44002695e+00], - [-1.18134453e+03, -2.18913373e-05, 1.30656109e+00], - [-5.00279505e+02, -4.59745390e-06, 1.09090465e+00] - ] - blackbody_table_b = [ - [0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0], - [-2.02524603e-11, 1.79435860e-07, -2.60561875e-04, -1.41761141e-02], - [-2.22463426e-13, -1.55078698e-08, 3.81675160e-04, -7.30646033e-01], - [6.72595954e-13, -2.73059993e-08, 4.24068546e-04, -7.52204323e-01] - ] - - if t >= 12000: - rgb[0] = 0.826270103 - rgb[1] = 0.994478524 - rgb[2] = 1.56626022 - - elif t < 965.0: - rgb[0] = 4.70366907 - rgb[1] = 0.0 - rgb[2] = 0.0 - - else: - if t >= 6365.0: - i = 5 - elif t >= 3315.0: - i = 4 - elif t >= 1902.0: - i = 3 - elif t >= 1449.0: - i = 2 - elif t >= 1167.0: - i = 1 - else: - i = 0 - - r = blackbody_table_r[i] - g = blackbody_table_g[i] - b = blackbody_table_b[i] - - t_inv = 1.0 / t - - rgb[0] = r[0] * t_inv + r[1] * t + r[2] - rgb[1] = g[0] * t_inv + g[1] * t + g[2] - rgb[2] = ((b[0] * t + b[1]) * t + b[2]) * t + b[3] - - # Pass constant - return c.to_vec3([rgb[0], rgb[1], rgb[2]]) + + t = c.parse_value_input(node.inputs[0]) + state.curshader.add_function(c_functions.str_blackbody) + return f'blackbody({t})' def parse_clamp(node: bpy.types.ShaderNodeClamp, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: value = c.parse_value_input(node.inputs['Value']) From 07741abc55bdba397d5884b69f36cea8289b97dc Mon Sep 17 00:00:00 2001 From: Alexander Kleemann Date: Tue, 23 Mar 2021 21:52:21 +0100 Subject: [PATCH 075/264] Implement map range node Map range node should now be fully supported --- blender/arm/material/cycles_functions.py | 63 +++++++++++++++++++ .../material/cycles_nodes/nodes_converter.py | 30 +++++++++ 2 files changed, 93 insertions(+) diff --git a/blender/arm/material/cycles_functions.py b/blender/arm/material/cycles_functions.py index 8135371b..1618c0a3 100644 --- a/blender/arm/material/cycles_functions.py +++ b/blender/arm/material/cycles_functions.py @@ -402,4 +402,67 @@ vec3 blackbody(const float temperature){ return rgb; } +""" + +# Adapted from https://github.com/blender/blender/blob/594f47ecd2d5367ca936cf6fc6ec8168c2b360d0/source/blender/gpu/shaders/material/gpu_shader_material_map_range.glsl +str_map_range_linear = """ +float map_range_linear(const float value, const float fromMin, const float fromMax, const float toMin, const float toMax) { + if (fromMax != fromMin) { + return float(toMin + ((value - fromMin) / (fromMax - fromMin)) * (toMax - toMin)); + } + else { + return float(0.0); + } +} +""" + +str_map_range_stepped = """ +float map_range_stepped(const float value, const float fromMin, const float fromMax, const float toMin, const float toMax, const float steps) { + if (fromMax != fromMin) { + float factor = (value - fromMin) / (fromMax - fromMin); + factor = (steps > 0.0) ? floor(factor * (steps + 1.0)) / steps : 0.0; + return float(toMin + factor * (toMax - toMin)); + } + else { + return float(0.0); + } +} +""" + +str_map_range_smoothstep = """ +float map_range_smoothstep(const float value, const float fromMin, const float fromMax, const float toMin, const float toMax) +{ + if (fromMax != fromMin) { + float factor = (fromMin > fromMax) ? 1.0 - smoothstep(fromMax, fromMin, value) : + smoothstep(fromMin, fromMax, value); + return float(toMin + factor * (toMax - toMin)); + } + else { + return float(0.0); + } +} +""" + +str_map_range_smootherstep = """ +float safe_divide(float a, float b) +{ + return (b != 0.0) ? a / b : 0.0; +} + +float smootherstep(float edge0, float edge1, float x) +{ + x = clamp(safe_divide((x - edge0), (edge1 - edge0)), 0.0, 1.0); + return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); +} + +float map_range_smootherstep(const float value, const float fromMin, const float fromMax, const float toMin, const float toMax) { + if (fromMax != fromMin) { + float factor = (fromMin > fromMax) ? 1.0 - smootherstep(fromMax, fromMin, value) : + smootherstep(fromMin, fromMax, value); + return float(toMin + factor * (toMax - toMin)); + } + else { + return float(0.0); + } +} """ \ No newline at end of file diff --git a/blender/arm/material/cycles_nodes/nodes_converter.py b/blender/arm/material/cycles_nodes/nodes_converter.py index a70c6559..9071471a 100644 --- a/blender/arm/material/cycles_nodes/nodes_converter.py +++ b/blender/arm/material/cycles_nodes/nodes_converter.py @@ -8,6 +8,36 @@ import arm.material.cycles_functions as c_functions from arm.material.parser_state import ParserState from arm.material.shader import floatstr, vec3str +def parse_maprange(node: bpy.types.ShaderNodeMapRange, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: + + interp = node.interpolation_type + + value: str = c.parse_value_input(node.inputs[0]) if node.inputs[0].is_linked else c.to_vec1(node.inputs[0].default_value) + fromMin = float(c.parse_value_input(node.inputs[1])) + fromMax = float(c.parse_value_input(node.inputs[2])) + toMin = float(c.parse_value_input(node.inputs[3])) + toMax = float(c.parse_value_input(node.inputs[4])) + + if interp == "LINEAR": + + state.curshader.add_function(c_functions.str_map_range_linear) + return f'map_range_linear({value}, {fromMin}, {fromMax}, {toMin}, {toMax})' + + elif interp == "STEPPED": + + steps = float(c.parse_value_input(node.inputs[5])) + state.curshader.add_function(c_functions.str_map_range_stepped) + return f'map_range_stepped({value}, {fromMin}, {fromMax}, {toMin}, {toMax}, {steps})' + + elif interp == "SMOOTHSTEP": + + state.curshader.add_function(c_functions.str_map_range_smoothstep) + return f'map_range_smoothstep({value}, {fromMin}, {fromMax}, {toMin}, {toMax})' + + elif interp == "SMOOTHERSTEP": + + state.curshader.add_function(c_functions.str_map_range_smootherstep) + return f'map_range_smootherstep({value}, {fromMin}, {fromMax}, {toMin}, {toMax})' def parse_blackbody(node: bpy.types.ShaderNodeBlackbody, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: From 5c1f8fe12827ab8d8df0a580c1f0162aa1cdb2db Mon Sep 17 00:00:00 2001 From: Alexander Kleemann Date: Tue, 23 Mar 2021 21:56:14 +0100 Subject: [PATCH 076/264] Implement vector rotate node Should now be fully supported --- blender/arm/material/cycles_functions.py | 49 +++++++++++++++++++ .../arm/material/cycles_nodes/nodes_vector.py | 30 ++++++++++++ 2 files changed, 79 insertions(+) diff --git a/blender/arm/material/cycles_functions.py b/blender/arm/material/cycles_functions.py index 1618c0a3..1bd3e340 100644 --- a/blender/arm/material/cycles_functions.py +++ b/blender/arm/material/cycles_functions.py @@ -465,4 +465,53 @@ float map_range_smootherstep(const float value, const float fromMin, const float return float(0.0); } } +""" + +str_rotate_around_axis = """ +vec3 rotate_around_axis(const vec3 p, const vec3 axis, const float angle) +{ + float costheta = cos(angle); + float sintheta = sin(angle); + vec3 r; + + r.x = ((costheta + (1.0 - costheta) * axis.x * axis.x) * p.x) + + (((1.0 - costheta) * axis.x * axis.y - axis.z * sintheta) * p.y) + + (((1.0 - costheta) * axis.x * axis.z + axis.y * sintheta) * p.z); + + r.y = (((1.0 - costheta) * axis.x * axis.y + axis.z * sintheta) * p.x) + + ((costheta + (1.0 - costheta) * axis.y * axis.y) * p.y) + + (((1.0 - costheta) * axis.y * axis.z - axis.x * sintheta) * p.z); + + r.z = (((1.0 - costheta) * axis.x * axis.z - axis.y * sintheta) * p.x) + + (((1.0 - costheta) * axis.y * axis.z + axis.x * sintheta) * p.y) + + ((costheta + (1.0 - costheta) * axis.z * axis.z) * p.z); + + return r; +} +""" + +str_euler_to_mat3 = """ +mat3 euler_to_mat3(vec3 euler) +{ + float cx = cos(euler.x); + float cy = cos(euler.y); + float cz = cos(euler.z); + float sx = sin(euler.x); + float sy = sin(euler.y); + float sz = sin(euler.z); + + mat3 mat; + mat[0][0] = cy * cz; + mat[0][1] = cy * sz; + mat[0][2] = -sy; + + mat[1][0] = sy * sx * cz - cx * sz; + mat[1][1] = sy * sx * sz + cx * cz; + mat[1][2] = cy * sx; + + mat[2][0] = sy * cx * cz + sx * sz; + mat[2][1] = sy * cx * sz - sx * cz; + mat[2][2] = cy * cx; + return mat; +} """ \ No newline at end of file diff --git a/blender/arm/material/cycles_nodes/nodes_vector.py b/blender/arm/material/cycles_nodes/nodes_vector.py index 5e35c7de..d18296b2 100644 --- a/blender/arm/material/cycles_nodes/nodes_vector.py +++ b/blender/arm/material/cycles_nodes/nodes_vector.py @@ -141,3 +141,33 @@ def parse_displacement(node: bpy.types.ShaderNodeDisplacement, out_socket: bpy.t scale = c.parse_value_input(node.inputs[2]) nor = c.parse_vector_input(node.inputs[3]) return f'(vec3({height}) * {scale})' + +def parse_vectorrotate(node: bpy.types.ShaderNodeVectorRotate, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: + + type = node.rotation_type + input_vector: bpy.types.NodeSocket = c.parse_vector_input(node.inputs[0]) + input_center: bpy.types.NodeSocket = c.parse_vector_input(node.inputs[1]) + input_axis: bpy.types.NodeSocket = c.parse_vector_input(node.inputs[2]) + input_angle: bpy.types.NodeSocket = c.parse_value_input(node.inputs[3]) + input_rotation: bpy.types.NodeSocket = c.parse_vector_input(node.inputs[4]) + + if node.invert: + input_invert = "0" + else: + input_invert = "1" + + state.curshader.add_function(c_functions.str_rotate_around_axis) + + if type == 'AXIS_ANGLE': + return f'vec3( (length({input_axis}) != 0.0) ? rotate_around_axis({input_vector} - {input_center}, normalize({input_axis}), {input_angle} * {input_invert}) + {input_center} : {input_vector} )' + elif type == 'X_AXIS': + return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(1.0, 0.0, 0.0), {input_angle} * {input_invert}) + {input_center} )' + elif type == 'Y_AXIS': + return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(0.0, 1.0, 0.0), {input_angle} * {input_invert}) + {input_center} )' + elif type == 'Z_AXIS': + return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(0.0, 0.0, 1.0), {input_angle} * {input_invert}) + {input_center} )' + elif type == 'EULER_XYZ': + state.curshader.add_function(c_functions.str_euler_to_mat3) + return f'vec3( mat3(({input_invert} < 0.0) ? transpose(euler_to_mat3({input_rotation})) : euler_to_mat3({input_rotation})) * ({input_vector} - {input_center}) + {input_center})' + + return f'(vec3(1.0, 0.0, 0.0))' \ No newline at end of file From 0cc2ea4491e0d6d621842afc020bc2607b466ad2 Mon Sep 17 00:00:00 2001 From: Alexander Kleemann Date: Tue, 23 Mar 2021 21:58:46 +0100 Subject: [PATCH 077/264] Add map range and vector rotate to node parser dictionary --- blender/arm/material/cycles.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/blender/arm/material/cycles.py b/blender/arm/material/cycles.py index 71cf6f84..fc128d85 100644 --- a/blender/arm/material/cycles.py +++ b/blender/arm/material/cycles.py @@ -331,6 +331,7 @@ def parse_vector(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> str: 'COMBXYZ': nodes_converter.parse_combxyz, 'VECT_MATH': nodes_converter.parse_vectormath, 'DISPLACEMENT': nodes_vector.parse_displacement, + 'VECTOR_ROTATE': nodes_vector.parse_vectorrotate, } if node.type in node_parser_funcs: @@ -433,6 +434,7 @@ def parse_value(node, socket): 'SEPRGB': nodes_converter.parse_seprgb, 'SEPXYZ': nodes_converter.parse_sepxyz, 'VECT_MATH': nodes_converter.parse_vectormath, + 'MAP_RANGE': nodes_converter.parse_maprange, } if node.type in node_parser_funcs: From dfe1a49f85f74f3760dfc8b725897e69e9464398 Mon Sep 17 00:00:00 2001 From: Alexander Kleemann Date: Tue, 23 Mar 2021 22:02:25 +0100 Subject: [PATCH 078/264] Implement the remaining 16 math node operations Math node is now fully supported --- .../material/cycles_nodes/nodes_converter.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/blender/arm/material/cycles_nodes/nodes_converter.py b/blender/arm/material/cycles_nodes/nodes_converter.py index 9071471a..046ae83e 100644 --- a/blender/arm/material/cycles_nodes/nodes_converter.py +++ b/blender/arm/material/cycles_nodes/nodes_converter.py @@ -232,14 +232,21 @@ def parse_math(node: bpy.types.ShaderNodeMath, out_socket: bpy.types.NodeSocket, out_val = '({0} * {1})'.format(val1, val2) elif op == 'DIVIDE': out_val = '({0} / {1})'.format(val1, val2) + elif op == 'MULTIPLY_ADD': + val3 = c.parse_value_input(node.inputs[2]) + out_val = '({0} * {1} + {2})'.format(val1, val2, val3) elif op == 'POWER': out_val = 'pow({0}, {1})'.format(val1, val2) elif op == 'LOGARITHM': out_val = 'log({0})'.format(val1) elif op == 'SQRT': out_val = 'sqrt({0})'.format(val1) + elif op == 'INVERSE_SQRT': + out_val = 'inversesqrt({0})'.format(val1) elif op == 'ABSOLUTE': out_val = 'abs({0})'.format(val1) + elif op == 'EXPONENT': + out_val = 'exp({0})'.format(val1) elif op == 'MINIMUM': out_val = 'min({0}, {1})'.format(val1, val2) elif op == 'MAXIMUM': @@ -248,6 +255,17 @@ def parse_math(node: bpy.types.ShaderNodeMath, out_socket: bpy.types.NodeSocket, out_val = 'float({0} < {1})'.format(val1, val2) elif op == 'GREATER_THAN': out_val = 'float({0} > {1})'.format(val1, val2) + elif op == 'SIGN': + out_val = 'sign({0})'.format(val1) + elif op == 'COMPARE': + val3 = c.parse_value_input(node.inputs[2]) + out_val = 'float((abs({0} - {1}) <= max({2}, 1e-5)) ? 1.0 : 0.0)'.format(val1, val2, val3) + elif op == 'SMOOTH_MIN': + val3 = c.parse_value_input(node.inputs[2]) + out_val = 'float(float({2} != 0.0 ? min({0},{1}) - (max({2} - abs({0} - {1}), 0.0) / {2}) * (max({2} - abs({0} - {1}), 0.0) / {2}) * (max({2} - abs({0} - {1}), 0.0) / {2}) * {2} * (1.0 / 6.0) : min({0}, {1})))'.format(val1, val2, val3) + elif op == 'SMOOTH_MAX': + val3 = c.parse_value_input(node.inputs[2]) + out_val = 'float(0-(float({2} != 0.0 ? min(-{0},-{1}) - (max({2} - abs(-{0} - (-{1})), 0.0) / {2}) * (max({2} - abs(-{0} - (-{1})), 0.0) / {2}) * (max({2} - abs(-{0} - (-{1})), 0.0) / {2}) * {2} * (1.0 / 6.0) : min(-{0}, (-{1})))))'.format(val1, val2, val3) elif op == 'ROUND': # out_val = 'round({0})'.format(val1) out_val = 'floor({0} + 0.5)'.format(val1) @@ -255,11 +273,20 @@ def parse_math(node: bpy.types.ShaderNodeMath, out_socket: bpy.types.NodeSocket, out_val = 'floor({0})'.format(val1) elif op == 'CEIL': out_val = 'ceil({0})'.format(val1) + elif op == 'TRUNC': + out_val = 'trunc({0})'.format(val1) elif op == 'FRACT': out_val = 'fract({0})'.format(val1) elif op == 'MODULO': # out_val = 'float({0} % {1})'.format(val1, val2) out_val = 'mod({0}, {1})'.format(val1, val2) + elif op == 'WRAP': + val3 = c.parse_value_input(node.inputs[2]) + out_val = 'float((({1}-{2}) != 0.0) ? {0} - (({1}-{2}) * floor(({0} - {2}) / ({1}-{2}))) : {2})'.format(val1, val2, val3) + elif op == 'SNAP': + out_val = 'floor(({1} != 0.0) ? {0} / {1} : 0.0) * {1}'.format(val1, val2) + elif op == 'PINGPONG': + out_val = 'float(({1} != 0.0) ? abs(fract(({0} - {1}) / ({1} * 2.0)) * {1} * 2.0 - {1}) : 0.0)'.format(val1, val2) elif op == 'SINE': out_val = 'sin({0})'.format(val1) elif op == 'COSINE': @@ -274,6 +301,16 @@ def parse_math(node: bpy.types.ShaderNodeMath, out_socket: bpy.types.NodeSocket, out_val = 'atan({0})'.format(val1) elif op == 'ARCTAN2': out_val = 'atan({0}, {1})'.format(val1, val2) + elif op == 'SINH': + out_val = 'sinh({0})'.format(val1) + elif op == 'COSH': + out_val = 'cosh({0})'.format(val1) + elif op == 'TANH': + out_val = 'tanh({0})'.format(val1) + elif op == 'RADIANS': + out_val = 'radians({0})'.format(val1) + elif op == 'DEGREES': + out_val = 'degrees({0})'.format(val1) if node.use_clamp: return 'clamp({0}, 0.0, 1.0)'.format(out_val) From 6e0bfa272ced1046639666faf203cd10ef99003f Mon Sep 17 00:00:00 2001 From: Alexander Kleemann Date: Wed, 24 Mar 2021 10:13:46 +0100 Subject: [PATCH 079/264] Remove float parsing --- blender/arm/material/cycles_nodes/nodes_converter.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/blender/arm/material/cycles_nodes/nodes_converter.py b/blender/arm/material/cycles_nodes/nodes_converter.py index 046ae83e..813a1d87 100644 --- a/blender/arm/material/cycles_nodes/nodes_converter.py +++ b/blender/arm/material/cycles_nodes/nodes_converter.py @@ -13,10 +13,10 @@ def parse_maprange(node: bpy.types.ShaderNodeMapRange, out_socket: bpy.types.Nod interp = node.interpolation_type value: str = c.parse_value_input(node.inputs[0]) if node.inputs[0].is_linked else c.to_vec1(node.inputs[0].default_value) - fromMin = float(c.parse_value_input(node.inputs[1])) - fromMax = float(c.parse_value_input(node.inputs[2])) - toMin = float(c.parse_value_input(node.inputs[3])) - toMax = float(c.parse_value_input(node.inputs[4])) + fromMin = c.parse_value_input(node.inputs[1]) + fromMax = c.parse_value_input(node.inputs[2]) + toMin = c.parse_value_input(node.inputs[3]) + toMax = c.parse_value_input(node.inputs[4]) if interp == "LINEAR": From 930b2968fab2f6a549b41357895420ed38d39065 Mon Sep 17 00:00:00 2001 From: Alexander Kleemann Date: Wed, 24 Mar 2021 10:20:05 +0100 Subject: [PATCH 080/264] Include cycles glsl functions for vector rotation --- blender/arm/material/cycles_nodes/nodes_vector.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/blender/arm/material/cycles_nodes/nodes_vector.py b/blender/arm/material/cycles_nodes/nodes_vector.py index d18296b2..5f78a70b 100644 --- a/blender/arm/material/cycles_nodes/nodes_vector.py +++ b/blender/arm/material/cycles_nodes/nodes_vector.py @@ -4,6 +4,7 @@ import bpy from mathutils import Euler, Vector import arm.material.cycles as c +import arm.material.cycles_functions as c_functions from arm.material.parser_state import ParserState from arm.material.shader import floatstr, vec3str @@ -170,4 +171,4 @@ def parse_vectorrotate(node: bpy.types.ShaderNodeVectorRotate, out_socket: bpy.t state.curshader.add_function(c_functions.str_euler_to_mat3) return f'vec3( mat3(({input_invert} < 0.0) ? transpose(euler_to_mat3({input_rotation})) : euler_to_mat3({input_rotation})) * ({input_vector} - {input_center}) + {input_center})' - return f'(vec3(1.0, 0.0, 0.0))' \ No newline at end of file + return f'(vec3(1.0, 0.0, 0.0))' From 24093d15df303bb29dd8c3f17ff4a9cecdb02fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 25 Mar 2021 22:02:59 +0100 Subject: [PATCH 081/264] Fix writing to gl_Position before instancing code --- blender/arm/material/make_mesh.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/blender/arm/material/make_mesh.py b/blender/arm/material/make_mesh.py index 9ec18bbe..09519dab 100644 --- a/blender/arm/material/make_mesh.py +++ b/blender/arm/material/make_mesh.py @@ -125,6 +125,10 @@ def make_base(con_mesh, parse_opacity): if write_material_attribs_post != None: write_material_attribs_post(con_mesh, frag) + vert.add_out('vec3 wnormal') + make_attrib.write_norpos(con_mesh, vert) + frag.write_attrib('vec3 n = normalize(wnormal);') + if not is_displacement and not vattr_written: make_attrib.write_vertpos(vert) @@ -162,10 +166,6 @@ def make_base(con_mesh, parse_opacity): make_tess.interpolate(tese, 'vcolor', 3, declare_out=frag.contains('vcolor')) tese.write_pre = False - vert.add_out('vec3 wnormal') - make_attrib.write_norpos(con_mesh, vert) - frag.write_attrib('vec3 n = normalize(wnormal);') - if con_mesh.is_elem('tang'): if tese is not None: tese.add_out('mat3 TBN') From 05c14238f25fa83d2679a479bbdac5c2d44b8278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 25 Mar 2021 23:03:08 +0100 Subject: [PATCH 082/264] shader.py: add API to set texture params --- blender/arm/material/shader.py | 36 +++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/blender/arm/material/shader.py b/blender/arm/material/shader.py index 7b00ed6d..b094a734 100644 --- a/blender/arm/material/shader.py +++ b/blender/arm/material/shader.py @@ -119,16 +119,29 @@ class ShaderContext: c['link'] = link self.constants.append(c) - def add_texture_unit(self, ctype, name, link=None, is_image=None): + def add_texture_unit(self, name, link=None, is_image=None, + addr_u=None, addr_v=None, + filter_min=None, filter_mag=None, mipmap_filter=None): for c in self.tunits: if c['name'] == name: return - c = { 'name': name } - if link != None: + c = {'name': name} + if link is not None: c['link'] = link - if is_image != None: + if is_image is not None: c['is_image'] = is_image + if addr_u is not None: + c['addressing_u'] = addr_u + if addr_v is not None: + c['addressing_v'] = addr_v + if filter_min is not None: + c['filter_min'] = filter_min + if filter_mag is not None: + c['filter_mag'] = filter_mag + if mipmap_filter is not None: + c['mipmap_filter'] = mipmap_filter + self.tunits.append(c) def make_vert(self, custom_name: str = None): @@ -213,7 +226,10 @@ class Shader: if s not in self.outs: self.outs.append(s) - def add_uniform(self, s, link=None, included=False): + def add_uniform(self, s, link=None, included=False, + tex_addr_u=None, tex_addr_v=None, + tex_filter_min=None, tex_filter_mag=None, + tex_mipmap_filter=None): ar = s.split(' ') # layout(RGBA8) image3D voxels utype = ar[-2] @@ -224,9 +240,15 @@ class Shader: # Add individual units - mySamplers[0], mySamplers[1] for i in range(int(uname[-2])): uname_array = uname[:-2] + str(i) + ']' - self.context.add_texture_unit(utype, uname_array, link=link, is_image=is_image) + self.context.add_texture_unit( + uname_array, link, is_image, + tex_addr_u, tex_addr_v, + tex_filter_min, tex_filter_mag, tex_mipmap_filter) else: - self.context.add_texture_unit(utype, uname, link=link, is_image=is_image) + self.context.add_texture_unit( + uname, link, is_image, + tex_addr_u, tex_addr_v, + tex_filter_min, tex_filter_mag, tex_mipmap_filter) else: # Prefer vec4[] for d3d to avoid padding if ar[0] == 'float' and '[' in ar[1]: From 8d812548c431e8820586c10fffc7d6afcfdec547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 25 Mar 2021 23:25:41 +0100 Subject: [PATCH 083/264] Use 2D LUT for Nishita skies --- Shaders/std/sky.glsl | 63 ++++--- Sources/armory/object/Uniforms.hx | 2 +- Sources/armory/renderpath/Nishita.hx | 158 ++++++++++++++---- .../material/cycles_nodes/nodes_texture.py | 2 + 4 files changed, 159 insertions(+), 66 deletions(-) diff --git a/Shaders/std/sky.glsl b/Shaders/std/sky.glsl index 61f93c84..5a58734f 100644 --- a/Shaders/std/sky.glsl +++ b/Shaders/std/sky.glsl @@ -1,12 +1,13 @@ /* Various sky functions * ===================== * - * Nishita model is based on https://github.com/wwwtyro/glsl-atmosphere(Unlicense License) + * Nishita model is based on https://github.com/wwwtyro/glsl-atmosphere (Unlicense License) * * Changes to the original implementation: * - r and pSun parameters of nishita_atmosphere() are already normalized * - Some original parameters of nishita_atmosphere() are replaced with pre-defined values * - Implemented air, dust and ozone density node parameters (see Blender source) + * - Replaced the inner integral calculation with a LUT lookup * * Reference for the sun's limb darkening and ozone calculations: * [Hill] Sebastien Hillaire. Physically Based Sky, Atmosphere and Cloud Rendering in Frostbite @@ -19,15 +20,18 @@ #ifndef _SKY_GLSL_ #define _SKY_GLSL_ -// OpenGl ES doesn't support 1D textures so we use a 1 px height sampler2D here... uniform sampler2D nishitaLUT; -#define PI 3.141592 +#ifndef PI + #define PI 3.141592 +#endif +#ifndef HALF_PI + #define HALF_PI 1.570796 +#endif #define nishita_iSteps 16 -#define nishita_jSteps 8 -// The values here are taken from Cycles code if they +// These values are taken from Cycles code if they // exist there, otherwise they are taken from the example // in the glsl-atmosphere repo #define nishita_sun_intensity 22.0 @@ -49,7 +53,17 @@ uniform sampler2D nishitaLUT; // Values from [Hill: 60] #define sun_limb_darkening_col vec3(0.397, 0.503, 0.652) -#define heightToLUT(h) (textureLod(nishitaLUT, vec2(clamp(h * (1 / 60000.0), 0.0, 1.0), 0.0), 0.0).xyz * 10.0) +float random(vec2 coords) { + return fract(sin(dot(coords.xy, vec2(12.9898,78.233))) * 43758.5453); +} + +vec3 nishita_lookupLUT(const float height, const float sunTheta) { + vec2 coords = vec2( + sqrt(height * (1 / nishita_atmo_radius)), + 0.5 + 0.5 * sign(sunTheta - HALF_PI) * sqrt(abs(sunTheta * (1 / HALF_PI) - 1)) + ); + return textureLod(nishitaLUT, coords, 0.0).rgb; +} /* Approximates the density of ozone for a given sample height. Values taken from Cycles code. */ float nishita_density_ozone(const float height) { @@ -112,40 +126,21 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa // Calculate the height of the sample. float iHeight = length(iPos) - rPlanet; - // Calculate the optical depth of the Rayleigh and Mie scattering for this step. - vec3 iLookup = heightToLUT(iHeight); - float odStepRlh = iLookup.x * iStepSize; - float odStepMie = iLookup.y * iStepSize; + // Calculate the optical depth of the Rayleigh and Mie scattering for this step + float odStepRlh = exp(-iHeight / nishita_rayleigh_scale) * density.x * iStepSize; + float odStepMie = exp(-iHeight / nishita_mie_scale) * density.y * iStepSize; // Accumulate optical depth. iOdRlh += odStepRlh; iOdMie += odStepMie; - // Calculate the step size of the secondary ray. - float jStepSize = nishita_rsi(iPos, pSun, nishita_atmo_radius).y / float(nishita_jSteps); + // Idea behind this: "Rotate" everything by iPos (-> iPos is the new zenith) and then all calculations for the + // inner integral only depend on the sample height (iHeight) and sunTheta (angle between sun and new zenith). + float sunTheta = acos(dot(normalize(iPos), normalize(pSun))); + vec3 jODepth = nishita_lookupLUT(iHeight, sunTheta);// * vec3(14000000 / 255, 14000000 / 255, 2000000 / 255); - // Initialize the secondary ray time. - float jTime = 0.0; - - // Initialize optical depth accumulators for the secondary ray. - vec3 jODepth = vec3(0.0); // (Rayleigh, Mie, ozone) - - // Sample the secondary ray. - for (int j = 0; j < nishita_jSteps; j++) { - - // Calculate the secondary ray sample position. - vec3 jPos = iPos + pSun * (jTime + jStepSize * 0.5); - - // Calculate the height of the sample. - float jHeight = length(jPos) - rPlanet; - - // Accumulate the optical depth. - vec3 jLookup = heightToLUT(jHeight); - jODepth += jLookup * jStepSize; - - // Increment the secondary ray time. - jTime += jStepSize; - } + // Apply dithering to reduce visible banding + jODepth += mix(-1000, 1000, random(r.xy)); // Calculate attenuation. vec3 attn = exp(-( diff --git a/Sources/armory/object/Uniforms.hx b/Sources/armory/object/Uniforms.hx index 64d20af2..e492f2fb 100644 --- a/Sources/armory/object/Uniforms.hx +++ b/Sources/armory/object/Uniforms.hx @@ -21,7 +21,7 @@ class Uniforms { public static function textureLink(object: Object, mat: MaterialData, link: String): kha.Image { if (link == "_nishitaLUT") { if (armory.renderpath.Nishita.data == null) armory.renderpath.Nishita.recompute(Scene.active.world); - return armory.renderpath.Nishita.data.optDepthLUT; + return armory.renderpath.Nishita.data.lut; } #if arm_ltc else if (link == "_ltcMat") { diff --git a/Sources/armory/renderpath/Nishita.hx b/Sources/armory/renderpath/Nishita.hx index c968798d..1a4cac92 100644 --- a/Sources/armory/renderpath/Nishita.hx +++ b/Sources/armory/renderpath/Nishita.hx @@ -5,36 +5,77 @@ import kha.graphics4.TextureFormat; import kha.graphics4.Usage; import iron.data.WorldData; +import iron.math.Vec2; +import iron.math.Vec3; import armory.math.Helper; +/** + Utility class to control the Nishita sky model. +**/ class Nishita { public static var data: NishitaData = null; + /** + Recompute the nishita lookup table. Call this function after updating + the sky density settings. + **/ public static function recompute(world: WorldData) { if (world == null || world.raw.sun_direction == null) return; if (data == null) data = new NishitaData(); // TODO - data.recompute(1.0, 1.0, 1.0); + data.computeLUT(new Vec3(1.0, 1.0, 1.0)); } } +/** + This class holds the precalculated result of the inner scattering integral + of the Nishita sky model. The outer integral is calculated in + [`armory/Shaders/std/sky.glsl`](https://github.com/armory3d/armory/blob/master/Shaders/std/sky.glsl). + + @see `armory.renderpath.Nishita` +**/ class NishitaData { - static inline var LUT_WIDTH = 16; - /** Maximum ray height as defined by Cycles **/ - static inline var MAX_HEIGHT = 60000; + public var lut: kha.Image; - static inline var RAYLEIGH_SCALE = 8e3; - static inline var MIE_SCALE = 1.2e3; + /** + The amount of individual sample heights stored in the LUT (and the width + of the LUT image). + **/ + public static var lutHeightSteps = 128; + /** + The amount of individual sun angle steps stored in the LUT (and the + height of the LUT image). + **/ + public static var lutAngleSteps = 128; - public var optDepthLUT: kha.Image; + /** + Amount of steps for calculating the inner scattering integral. Heigher + values are more precise but take longer to compute. + **/ + public static var jSteps = 8; + + /** Radius of the atmosphere in meters. **/ + public static var radiusAtmo = 6420000; + /** + Radius of the planet in meters. The default value is the earth radius as + defined in Cycles. + **/ + public static var radiusPlanet = 6360000; + + /** Rayleigh scattering scale parameter. **/ + public static var rayleighScale = 8e3; + /** Mie scattering scale parameter. **/ + public static var mieScale = 1.2e3; public function new() {} + /** Approximates the density of ozone for a given sample height. **/ function getOzoneDensity(height: FastFloat): FastFloat { + // Values are taken from Cycles code if (height < 10000.0 || height >= 40000.0) { return 0.0; } @@ -45,36 +86,91 @@ class NishitaData { } /** - The RGBA texture layout looks as follows: - R = Rayleigh optical depth at height \in [0, 60000] - G = Mie optical depth at height \in [0, 60000] - B = Ozone optical depth at height \in [0, 60000] - A = Unused + Ray-sphere intersection test that assumes the sphere is centered at the + origin. There is no intersection when result.x > result.y. Otherwise + this function returns the distances to the two intersection points, + which might be equal. **/ - public function recompute(densityFacAir: FastFloat, densityFacDust: FastFloat, densityFacOzone: FastFloat) { - optDepthLUT = kha.Image.create(LUT_WIDTH, 1, TextureFormat.RGBA32, Usage.StaticUsage); + function raySphereIntersection(rayOrigin: Vec3, rayDirection: Vec3, sphereRadius: Int): Vec2 { + // Algorithm is described here: https://en.wikipedia.org/wiki/Line%E2%80%93sphere_intersection + var a = rayDirection.dot(rayDirection); + var b = 2.0 * rayDirection.dot(rayOrigin); + var c = rayOrigin.dot(rayOrigin) - (sphereRadius * sphereRadius); + var d = (b * b) - 4.0 * a * c; - var textureData = optDepthLUT.lock(); - for (i in 0...LUT_WIDTH) { - // Get the height for each LUT pixel i (in [-1, 1] range) - var height = (i / LUT_WIDTH) * 2 - 1; + // Ray does not intersect the sphere + if (d < 0.0) return new Vec2(1e5, -1e5); + + return new Vec2( + (-b - Math.sqrt(d)) / (2.0 * a), + (-b + Math.sqrt(d)) / (2.0 * a) + ); + } + + /** + Computes the LUT texture for the given density values. + @param density 3D vector of air density, dust density, ozone density + **/ + public function computeLUT(density: Vec3) { + var imageData = new haxe.io.Float32Array(lutHeightSteps * lutAngleSteps * 4); + + for (x in 0...lutHeightSteps) { + var height = (x / (lutHeightSteps - 1)); // Use quadratic height for better horizon precision - // See https://sebh.github.io/publications/egsr2020.pdf (5.3) - height = 0.5 + 0.5 * Helper.sign(height) * Math.sqrt(Math.abs(height)); - height *= MAX_HEIGHT; // Denormalize + height *= height; + height *= radiusAtmo; // Denormalize - // Make sure we use 32 bit floats - var optDepthRayleigh: FastFloat = Math.exp(-height / RAYLEIGH_SCALE) * densityFacAir; - var optDepthMie: FastFloat = Math.exp(-height / MIE_SCALE) * densityFacDust; - var optDepthOzone: FastFloat = getOzoneDensity(height) * densityFacOzone; + for (y in 0...lutAngleSteps) { + var sunTheta = y / (lutAngleSteps - 1) * 2 - 1; - // 10 is the maximum density, so we divide by it to be able to use normalized values - textureData.set(i * 4 + 0, Std.int(optDepthRayleigh * 255 / 10)); - textureData.set(i * 4 + 1, Std.int(optDepthMie * 255 / 10)); - textureData.set(i * 4 + 2, Std.int(optDepthOzone * 255 / 10)); - textureData.set(i * 4 + 3, 255); // Unused + // Improve horizon precision + // See https://sebh.github.io/publications/egsr2020.pdf (5.3) + sunTheta = Helper.sign(sunTheta) * sunTheta * sunTheta; + sunTheta = sunTheta * Math.PI / 2 + Math.PI / 2; // Denormalize + + var jODepth = sampleSecondaryRay(height, sunTheta, density); + + var pixelIndex = (x + y * lutHeightSteps) * 4; + imageData[pixelIndex + 0] = jODepth.x; + imageData[pixelIndex + 1] = jODepth.y; + imageData[pixelIndex + 2] = jODepth.z; + imageData[pixelIndex + 3] = 1.0; // Unused + } } - optDepthLUT.unlock(); + + lut = kha.Image.fromBytes(imageData.view.buffer, lutHeightSteps, lutAngleSteps, TextureFormat.RGBA128, Usage.StaticUsage); + } + + /** + Calculates the integral for the secondary ray. + **/ + public function sampleSecondaryRay(height: FastFloat, sunTheta: FastFloat, density: Vec3): Vec3 { + // Reconstruct values from the shader + var iPos = new Vec3(0, 0, height + radiusPlanet); + var pSun = new Vec3(0.0, Math.sin(sunTheta), Math.cos(sunTheta)).normalize(); + + var jTime: FastFloat = 0.0; + var jStepSize: FastFloat = raySphereIntersection(iPos, pSun, radiusAtmo).y / jSteps; + + // Optical depth accumulators for the secondary ray (Rayleigh, Mie, ozone) + var jODepth = new Vec3(); + + for (i in 0...jSteps) { + + // Calculate the secondary ray sample position and height + var jPos = iPos.clone().add(pSun.clone().mult(jTime + jStepSize * 0.5)); + var jHeight = jPos.length() - radiusPlanet; + + // Accumulate optical depth + var optDepthRayleigh = Math.exp(-jHeight / rayleighScale) * density.x; + var optDepthMie = Math.exp(-jHeight / mieScale) * density.y; + var optDepthOzone = getOzoneDensity(jHeight) * density.z; + jODepth.addf(optDepthRayleigh, optDepthMie, optDepthOzone); + + jTime += jStepSize; + } + + return jODepth.mult(jStepSize); } } diff --git a/blender/arm/material/cycles_nodes/nodes_texture.py b/blender/arm/material/cycles_nodes/nodes_texture.py index b3b81d1f..c88a8373 100644 --- a/blender/arm/material/cycles_nodes/nodes_texture.py +++ b/blender/arm/material/cycles_nodes/nodes_texture.py @@ -373,6 +373,8 @@ def parse_sky_nishita(node: bpy.types.ShaderNodeTexSky, state: ParserState) -> v curshader = state.curshader curshader.add_include('std/sky.glsl') curshader.add_uniform('vec3 sunDir', link='_sunDirection') + curshader.add_uniform('sampler2D nishitaLUT', link='_nishitaLUT', included=True, + tex_addr_u='clamp', tex_addr_v='clamp') planet_radius = 6360e3 # Earth radius used in Blender ray_origin_z = planet_radius + node.altitude From 845d2aff9376074d11ddee443a24d5d2ecbda647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Fri, 26 Mar 2021 15:39:18 +0100 Subject: [PATCH 084/264] Use switch/case for uniform links --- Sources/armory/object/Uniforms.hx | 305 +++++++++++++++--------------- 1 file changed, 156 insertions(+), 149 deletions(-) diff --git a/Sources/armory/object/Uniforms.hx b/Sources/armory/object/Uniforms.hx index e492f2fb..e87f809b 100644 --- a/Sources/armory/object/Uniforms.hx +++ b/Sources/armory/object/Uniforms.hx @@ -19,172 +19,179 @@ class Uniforms { } public static function textureLink(object: Object, mat: MaterialData, link: String): kha.Image { - if (link == "_nishitaLUT") { - if (armory.renderpath.Nishita.data == null) armory.renderpath.Nishita.recompute(Scene.active.world); - return armory.renderpath.Nishita.data.lut; + switch (link) { + case "_nishitaLUT": { + if (armory.renderpath.Nishita.data == null) armory.renderpath.Nishita.recompute(Scene.active.world); + return armory.renderpath.Nishita.data.lut; + } + #if arm_ltc + case "_ltcMat": { + if (armory.data.ConstData.ltcMatTex == null) armory.data.ConstData.initLTC(); + return armory.data.ConstData.ltcMatTex; + } + case "_ltcMag": { + if (armory.data.ConstData.ltcMagTex == null) armory.data.ConstData.initLTC(); + return armory.data.ConstData.ltcMagTex; + } + #end } - #if arm_ltc - else if (link == "_ltcMat") { - if (armory.data.ConstData.ltcMatTex == null) armory.data.ConstData.initLTC(); - return armory.data.ConstData.ltcMatTex; - } - else if (link == "_ltcMag") { - if (armory.data.ConstData.ltcMagTex == null) armory.data.ConstData.initLTC(); - return armory.data.ConstData.ltcMagTex; - } - #end + var target = iron.RenderPath.active.renderTargets.get(link.endsWith("_depth") ? link.substr(0, link.length - 6) : link); return target != null ? target.image : null; } public static function vec3Link(object: Object, mat: MaterialData, link: String): iron.math.Vec4 { var v: Vec4 = null; - #if arm_hosek - if (link == "_hosekA") { - if (armory.renderpath.HosekWilkie.data == null) { - armory.renderpath.HosekWilkie.recompute(Scene.active.world); + switch (link) { + #if arm_hosek + case "_hosekA": { + if (armory.renderpath.HosekWilkie.data == null) { + armory.renderpath.HosekWilkie.recompute(Scene.active.world); + } + if (armory.renderpath.HosekWilkie.data != null) { + v = iron.object.Uniforms.helpVec; + v.x = armory.renderpath.HosekWilkie.data.A.x; + v.y = armory.renderpath.HosekWilkie.data.A.y; + v.z = armory.renderpath.HosekWilkie.data.A.z; + } } - if (armory.renderpath.HosekWilkie.data != null) { + case "_hosekB": { + if (armory.renderpath.HosekWilkie.data == null) { + armory.renderpath.HosekWilkie.recompute(Scene.active.world); + } + if (armory.renderpath.HosekWilkie.data != null) { + v = iron.object.Uniforms.helpVec; + v.x = armory.renderpath.HosekWilkie.data.B.x; + v.y = armory.renderpath.HosekWilkie.data.B.y; + v.z = armory.renderpath.HosekWilkie.data.B.z; + } + } + case "_hosekC": { + if (armory.renderpath.HosekWilkie.data == null) { + armory.renderpath.HosekWilkie.recompute(Scene.active.world); + } + if (armory.renderpath.HosekWilkie.data != null) { + v = iron.object.Uniforms.helpVec; + v.x = armory.renderpath.HosekWilkie.data.C.x; + v.y = armory.renderpath.HosekWilkie.data.C.y; + v.z = armory.renderpath.HosekWilkie.data.C.z; + } + } + case "_hosekD": { + if (armory.renderpath.HosekWilkie.data == null) { + armory.renderpath.HosekWilkie.recompute(Scene.active.world); + } + if (armory.renderpath.HosekWilkie.data != null) { + v = iron.object.Uniforms.helpVec; + v.x = armory.renderpath.HosekWilkie.data.D.x; + v.y = armory.renderpath.HosekWilkie.data.D.y; + v.z = armory.renderpath.HosekWilkie.data.D.z; + } + } + case "_hosekE": { + if (armory.renderpath.HosekWilkie.data == null) { + armory.renderpath.HosekWilkie.recompute(Scene.active.world); + } + if (armory.renderpath.HosekWilkie.data != null) { + v = iron.object.Uniforms.helpVec; + v.x = armory.renderpath.HosekWilkie.data.E.x; + v.y = armory.renderpath.HosekWilkie.data.E.y; + v.z = armory.renderpath.HosekWilkie.data.E.z; + } + } + case "_hosekF": { + if (armory.renderpath.HosekWilkie.data == null) { + armory.renderpath.HosekWilkie.recompute(Scene.active.world); + } + if (armory.renderpath.HosekWilkie.data != null) { + v = iron.object.Uniforms.helpVec; + v.x = armory.renderpath.HosekWilkie.data.F.x; + v.y = armory.renderpath.HosekWilkie.data.F.y; + v.z = armory.renderpath.HosekWilkie.data.F.z; + } + } + case "_hosekG": { + if (armory.renderpath.HosekWilkie.data == null) { + armory.renderpath.HosekWilkie.recompute(Scene.active.world); + } + if (armory.renderpath.HosekWilkie.data != null) { + v = iron.object.Uniforms.helpVec; + v.x = armory.renderpath.HosekWilkie.data.G.x; + v.y = armory.renderpath.HosekWilkie.data.G.y; + v.z = armory.renderpath.HosekWilkie.data.G.z; + } + } + case "_hosekH": { + if (armory.renderpath.HosekWilkie.data == null) { + armory.renderpath.HosekWilkie.recompute(Scene.active.world); + } + if (armory.renderpath.HosekWilkie.data != null) { + v = iron.object.Uniforms.helpVec; + v.x = armory.renderpath.HosekWilkie.data.H.x; + v.y = armory.renderpath.HosekWilkie.data.H.y; + v.z = armory.renderpath.HosekWilkie.data.H.z; + } + } + case "_hosekI": { + if (armory.renderpath.HosekWilkie.data == null) { + armory.renderpath.HosekWilkie.recompute(Scene.active.world); + } + if (armory.renderpath.HosekWilkie.data != null) { + v = iron.object.Uniforms.helpVec; + v.x = armory.renderpath.HosekWilkie.data.I.x; + v.y = armory.renderpath.HosekWilkie.data.I.y; + v.z = armory.renderpath.HosekWilkie.data.I.z; + } + } + case "_hosekZ": { + if (armory.renderpath.HosekWilkie.data == null) { + armory.renderpath.HosekWilkie.recompute(Scene.active.world); + } + if (armory.renderpath.HosekWilkie.data != null) { + v = iron.object.Uniforms.helpVec; + v.x = armory.renderpath.HosekWilkie.data.Z.x; + v.y = armory.renderpath.HosekWilkie.data.Z.y; + v.z = armory.renderpath.HosekWilkie.data.Z.z; + } + } + #end + #if rp_voxelao + case "_cameraPositionSnap": { v = iron.object.Uniforms.helpVec; - v.x = armory.renderpath.HosekWilkie.data.A.x; - v.y = armory.renderpath.HosekWilkie.data.A.y; - v.z = armory.renderpath.HosekWilkie.data.A.z; + var camera = iron.Scene.active.camera; + v.set(camera.transform.worldx(), camera.transform.worldy(), camera.transform.worldz()); + var l = camera.lookWorld(); + var e = Main.voxelgiHalfExtents; + v.x += l.x * e * 0.9; + v.y += l.y * e * 0.9; + var f = Main.voxelgiVoxelSize * 8; // Snaps to 3 mip-maps range + v.set(Math.floor(v.x / f) * f, Math.floor(v.y / f) * f, Math.floor(v.z / f) * f); } + #end } - else if (link == "_hosekB") { - if (armory.renderpath.HosekWilkie.data == null) { - armory.renderpath.HosekWilkie.recompute(Scene.active.world); - } - if (armory.renderpath.HosekWilkie.data != null) { - v = iron.object.Uniforms.helpVec; - v.x = armory.renderpath.HosekWilkie.data.B.x; - v.y = armory.renderpath.HosekWilkie.data.B.y; - v.z = armory.renderpath.HosekWilkie.data.B.z; - } - } - else if (link == "_hosekC") { - if (armory.renderpath.HosekWilkie.data == null) { - armory.renderpath.HosekWilkie.recompute(Scene.active.world); - } - if (armory.renderpath.HosekWilkie.data != null) { - v = iron.object.Uniforms.helpVec; - v.x = armory.renderpath.HosekWilkie.data.C.x; - v.y = armory.renderpath.HosekWilkie.data.C.y; - v.z = armory.renderpath.HosekWilkie.data.C.z; - } - } - else if (link == "_hosekD") { - if (armory.renderpath.HosekWilkie.data == null) { - armory.renderpath.HosekWilkie.recompute(Scene.active.world); - } - if (armory.renderpath.HosekWilkie.data != null) { - v = iron.object.Uniforms.helpVec; - v.x = armory.renderpath.HosekWilkie.data.D.x; - v.y = armory.renderpath.HosekWilkie.data.D.y; - v.z = armory.renderpath.HosekWilkie.data.D.z; - } - } - else if (link == "_hosekE") { - if (armory.renderpath.HosekWilkie.data == null) { - armory.renderpath.HosekWilkie.recompute(Scene.active.world); - } - if (armory.renderpath.HosekWilkie.data != null) { - v = iron.object.Uniforms.helpVec; - v.x = armory.renderpath.HosekWilkie.data.E.x; - v.y = armory.renderpath.HosekWilkie.data.E.y; - v.z = armory.renderpath.HosekWilkie.data.E.z; - } - } - else if (link == "_hosekF") { - if (armory.renderpath.HosekWilkie.data == null) { - armory.renderpath.HosekWilkie.recompute(Scene.active.world); - } - if (armory.renderpath.HosekWilkie.data != null) { - v = iron.object.Uniforms.helpVec; - v.x = armory.renderpath.HosekWilkie.data.F.x; - v.y = armory.renderpath.HosekWilkie.data.F.y; - v.z = armory.renderpath.HosekWilkie.data.F.z; - } - } - else if (link == "_hosekG") { - if (armory.renderpath.HosekWilkie.data == null) { - armory.renderpath.HosekWilkie.recompute(Scene.active.world); - } - if (armory.renderpath.HosekWilkie.data != null) { - v = iron.object.Uniforms.helpVec; - v.x = armory.renderpath.HosekWilkie.data.G.x; - v.y = armory.renderpath.HosekWilkie.data.G.y; - v.z = armory.renderpath.HosekWilkie.data.G.z; - } - } - else if (link == "_hosekH") { - if (armory.renderpath.HosekWilkie.data == null) { - armory.renderpath.HosekWilkie.recompute(Scene.active.world); - } - if (armory.renderpath.HosekWilkie.data != null) { - v = iron.object.Uniforms.helpVec; - v.x = armory.renderpath.HosekWilkie.data.H.x; - v.y = armory.renderpath.HosekWilkie.data.H.y; - v.z = armory.renderpath.HosekWilkie.data.H.z; - } - } - else if (link == "_hosekI") { - if (armory.renderpath.HosekWilkie.data == null) { - armory.renderpath.HosekWilkie.recompute(Scene.active.world); - } - if (armory.renderpath.HosekWilkie.data != null) { - v = iron.object.Uniforms.helpVec; - v.x = armory.renderpath.HosekWilkie.data.I.x; - v.y = armory.renderpath.HosekWilkie.data.I.y; - v.z = armory.renderpath.HosekWilkie.data.I.z; - } - } - else if (link == "_hosekZ") { - if (armory.renderpath.HosekWilkie.data == null) { - armory.renderpath.HosekWilkie.recompute(Scene.active.world); - } - if (armory.renderpath.HosekWilkie.data != null) { - v = iron.object.Uniforms.helpVec; - v.x = armory.renderpath.HosekWilkie.data.Z.x; - v.y = armory.renderpath.HosekWilkie.data.Z.y; - v.z = armory.renderpath.HosekWilkie.data.Z.z; - } - } - #end - #if rp_voxelao - if (link == "_cameraPositionSnap") { - v = iron.object.Uniforms.helpVec; - var camera = iron.Scene.active.camera; - v.set(camera.transform.worldx(), camera.transform.worldy(), camera.transform.worldz()); - var l = camera.lookWorld(); - var e = Main.voxelgiHalfExtents; - v.x += l.x * e * 0.9; - v.y += l.y * e * 0.9; - var f = Main.voxelgiVoxelSize * 8; // Snaps to 3 mip-maps range - v.set(Math.floor(v.x / f) * f, Math.floor(v.y / f) * f, Math.floor(v.z / f) * f); - } - #end return v; } public static function floatLink(object: Object, mat: MaterialData, link: String): Null { - #if rp_dynres - if (link == "_dynamicScale") { - return armory.renderpath.DynamicResolutionScale.dynamicScale; + switch (link) { + #if rp_dynres + case "_dynamicScale": { + return armory.renderpath.DynamicResolutionScale.dynamicScale; + } + #end + #if arm_debug + case "_debugFloat": { + return armory.trait.internal.DebugConsole.debugFloat; + } + #end + #if rp_voxelao + case "_voxelBlend": { // Blend current and last voxels + var freq = armory.renderpath.RenderPathCreator.voxelFreq; + return (armory.renderpath.RenderPathCreator.voxelFrame % freq) / freq; + } + #end } - #end - #if arm_debug - if (link == "_debugFloat") { - return armory.trait.internal.DebugConsole.debugFloat; - } - #end - #if rp_voxelao - if (link == "_voxelBlend") { // Blend current and last voxels - var freq = armory.renderpath.RenderPathCreator.voxelFreq; - return (armory.renderpath.RenderPathCreator.voxelFrame % freq) / freq; - } - #end return null; } } From 420033c86dcb79ef783a25971660a8ba2d84c57d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Fri, 26 Mar 2021 20:59:26 +0100 Subject: [PATCH 085/264] Add API to set Nishita density parameters --- Shaders/std/sky.glsl | 8 +++---- Sources/armory/object/Uniforms.hx | 23 +++++++++++++++--- Sources/armory/renderpath/Nishita.hx | 24 +++++++++++++++---- blender/arm/exporter.py | 1 + .../material/cycles_nodes/nodes_texture.py | 5 ++-- blender/arm/props.py | 1 + 6 files changed, 48 insertions(+), 14 deletions(-) diff --git a/Shaders/std/sky.glsl b/Shaders/std/sky.glsl index 5a58734f..6fca94dd 100644 --- a/Shaders/std/sky.glsl +++ b/Shaders/std/sky.glsl @@ -21,6 +21,7 @@ #define _SKY_GLSL_ uniform sampler2D nishitaLUT; +uniform vec2 nishitaDensity; #ifndef PI #define PI 3.141592 @@ -91,9 +92,8 @@ vec2 nishita_rsi(const vec3 r0, const vec3 rd, const float sr) { * r0: ray origin * pSun: normalized sun direction * rPlanet: planet radius - * density: (air density, dust density, ozone density) */ -vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const float rPlanet, const vec3 density) { +vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const float rPlanet) { // Calculate the step size of the primary ray. vec2 p = nishita_rsi(r0, r, nishita_atmo_radius); if (p.x > p.y) return vec3(0,0,0); @@ -127,8 +127,8 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa float iHeight = length(iPos) - rPlanet; // Calculate the optical depth of the Rayleigh and Mie scattering for this step - float odStepRlh = exp(-iHeight / nishita_rayleigh_scale) * density.x * iStepSize; - float odStepMie = exp(-iHeight / nishita_mie_scale) * density.y * iStepSize; + float odStepRlh = exp(-iHeight / nishita_rayleigh_scale) * nishitaDensity.x * iStepSize; + float odStepMie = exp(-iHeight / nishita_mie_scale) * nishitaDensity.y * iStepSize; // Accumulate optical depth. iOdRlh += odStepRlh; diff --git a/Sources/armory/object/Uniforms.hx b/Sources/armory/object/Uniforms.hx index e87f809b..9416e79c 100644 --- a/Sources/armory/object/Uniforms.hx +++ b/Sources/armory/object/Uniforms.hx @@ -11,14 +11,14 @@ class Uniforms { public static function register() { iron.object.Uniforms.externalTextureLinks = [textureLink]; - iron.object.Uniforms.externalVec2Links = []; + iron.object.Uniforms.externalVec2Links = [vec2Link]; iron.object.Uniforms.externalVec3Links = [vec3Link]; iron.object.Uniforms.externalVec4Links = []; iron.object.Uniforms.externalFloatLinks = [floatLink]; iron.object.Uniforms.externalIntLinks = []; } - public static function textureLink(object: Object, mat: MaterialData, link: String): kha.Image { + public static function textureLink(object: Object, mat: MaterialData, link: String): Null { switch (link) { case "_nishitaLUT": { if (armory.renderpath.Nishita.data == null) armory.renderpath.Nishita.recompute(Scene.active.world); @@ -40,7 +40,7 @@ class Uniforms { return target != null ? target.image : null; } - public static function vec3Link(object: Object, mat: MaterialData, link: String): iron.math.Vec4 { + public static function vec3Link(object: Object, mat: MaterialData, link: String): Null { var v: Vec4 = null; switch (link) { #if arm_hosek @@ -173,6 +173,23 @@ class Uniforms { return v; } + public static function vec2Link(object: Object, mat: MaterialData, link: String): Null { + var v: Vec4 = null; + switch (link) { + case "_nishitaDensity": { + v = iron.object.Uniforms.helpVec; + var w = Scene.active.world; + if (w != null) { + // We only need Rayleigh and Mie density in the sky shader -> Vec2 + v.x = w.raw.nishita_density[0]; + v.y = w.raw.nishita_density[1]; + } + } + } + + return v; + } + public static function floatLink(object: Object, mat: MaterialData, link: String): Null { switch (link) { #if rp_dynres diff --git a/Sources/armory/renderpath/Nishita.hx b/Sources/armory/renderpath/Nishita.hx index 1a4cac92..7dcba4a2 100644 --- a/Sources/armory/renderpath/Nishita.hx +++ b/Sources/armory/renderpath/Nishita.hx @@ -1,6 +1,7 @@ package armory.renderpath; import kha.FastFloat; +import kha.arrays.Float32Array; import kha.graphics4.TextureFormat; import kha.graphics4.Usage; @@ -18,15 +19,28 @@ class Nishita { public static var data: NishitaData = null; /** - Recompute the nishita lookup table. Call this function after updating - the sky density settings. + Recomputes the nishita lookup table after the density settings changed. + Do not call this method on every frame (it's slow)! **/ public static function recompute(world: WorldData) { - if (world == null || world.raw.sun_direction == null) return; + if (world == null || world.raw.nishita_density == null) return; if (data == null) data = new NishitaData(); - // TODO - data.computeLUT(new Vec3(1.0, 1.0, 1.0)); + var density = world.raw.nishita_density; + data.computeLUT(new Vec3(density[0], density[1], density[2])); + } + + /** Sets the sky's density parameters and calls `recompute()` afterwards. **/ + public static function setDensity(world: WorldData, densityAir: FastFloat, densityDust: FastFloat, densityOzone: FastFloat) { + if (world == null) return; + + if (world.raw.nishita_density == null) world.raw.nishita_density = new Float32Array(3); + var density = world.raw.nishita_density; + density[0] = Helper.clamp(densityAir, 0, 10); + density[1] = Helper.clamp(densityDust, 0, 10); + density[2] = Helper.clamp(densityOzone, 0, 10); + + recompute(world); } } diff --git a/blender/arm/exporter.py b/blender/arm/exporter.py index f58039cf..e3841a28 100755 --- a/blender/arm/exporter.py +++ b/blender/arm/exporter.py @@ -2843,6 +2843,7 @@ class ArmoryExporter: out_world['sun_direction'] = list(world.arm_envtex_sun_direction) out_world['turbidity'] = world.arm_envtex_turbidity out_world['ground_albedo'] = world.arm_envtex_ground_albedo + out_world['nishita_density'] = list(world.arm_nishita_density) disable_hdr = world.arm_envtex_name.endswith('.jpg') diff --git a/blender/arm/material/cycles_nodes/nodes_texture.py b/blender/arm/material/cycles_nodes/nodes_texture.py index c88a8373..f9c35dd8 100644 --- a/blender/arm/material/cycles_nodes/nodes_texture.py +++ b/blender/arm/material/cycles_nodes/nodes_texture.py @@ -375,11 +375,12 @@ def parse_sky_nishita(node: bpy.types.ShaderNodeTexSky, state: ParserState) -> v curshader.add_uniform('vec3 sunDir', link='_sunDirection') curshader.add_uniform('sampler2D nishitaLUT', link='_nishitaLUT', included=True, tex_addr_u='clamp', tex_addr_v='clamp') + curshader.add_uniform('vec2 nishitaDensity', link='_nishitaDensity', included=True) planet_radius = 6360e3 # Earth radius used in Blender ray_origin_z = planet_radius + node.altitude - density = c.to_vec3((node.air_density, node.dust_density, node.ozone_density)) + state.world.arm_nishita_density = [node.air_density, node.dust_density, node.ozone_density] sun = '' if node.sun_disc: @@ -399,7 +400,7 @@ def parse_sky_nishita(node: bpy.types.ShaderNodeTexSky, state: ParserState) -> v size = math.cos(theta) sun = f'* sun_disk(n, sunDir, {size}, {node.sun_intensity})' - return f'nishita_atmosphere(n, vec3(0, 0, {ray_origin_z}), sunDir, {planet_radius}, {density}){sun}' + return f'nishita_atmosphere(n, vec3(0, 0, {ray_origin_z}), sunDir, {planet_radius}){sun}' def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: diff --git a/blender/arm/props.py b/blender/arm/props.py index 1c5b1c5b..b286cd5e 100755 --- a/blender/arm/props.py +++ b/blender/arm/props.py @@ -326,6 +326,7 @@ def init_properties(): bpy.types.World.arm_envtex_sun_direction = FloatVectorProperty(name="Sun Direction", size=3, default=[0,0,0]) bpy.types.World.arm_envtex_turbidity = FloatProperty(name="Turbidity", default=1.0) bpy.types.World.arm_envtex_ground_albedo = FloatProperty(name="Ground Albedo", default=0.0) + bpy.types.World.arm_nishita_density = FloatVectorProperty(name="Nishita Density", size=3, default=[1, 1, 1]) bpy.types.Material.arm_cast_shadow = BoolProperty(name="Cast Shadow", default=True) bpy.types.Material.arm_receive_shadow = BoolProperty(name="Receive Shadow", description="Requires forward render path", default=True) bpy.types.Material.arm_overlay = BoolProperty(name="Overlay", default=False) From de8073732a37acdb66eb130446050d4182d71469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 27 Mar 2021 00:30:05 +0100 Subject: [PATCH 086/264] Replace ' and " characters in asset names --- blender/arm/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/utils.py b/blender/arm/utils.py index 0c0cdc43..88db6888 100755 --- a/blender/arm/utils.py +++ b/blender/arm/utils.py @@ -576,7 +576,7 @@ def safesrc(s): def safestr(s: str) -> str: """Outputs a string where special characters have been replaced with '_', which can be safely used in file and path names.""" - for c in r'[]/\;,><&*:%=+@!#^()|?^': + for c in r'''[]/\;,><&*:%=+@!#^()|?^'"''': s = s.replace(c, '_') return ''.join([i if ord(i) < 128 else '_' for i in s]) From ead7dc9d322a18bcec04286ecb565d09c127f243 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Mon, 29 Mar 2021 10:41:46 -0300 Subject: [PATCH 087/264] Moved biasLWVP matrix uniform reference to a new one for sun This was done because the original "_biasLightWorldViewProjectionMatrix" relies on renderpath.light, which is problematic when rendering the deferred light pass and there is a sun and other lights of different type on the scene. Which would result on the wrong light being picked up for the calculation of the uniform value. --- Shaders/deferred_light/deferred_light.json | 2 +- Shaders/deferred_light_mobile/deferred_light_mobile.json | 2 +- Shaders/volumetric_light/volumetric_light.json | 2 +- blender/arm/material/make_mesh.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Shaders/deferred_light/deferred_light.json b/Shaders/deferred_light/deferred_light.json index d0d02f1f..efc70925 100755 --- a/Shaders/deferred_light/deferred_light.json +++ b/Shaders/deferred_light/deferred_light.json @@ -99,7 +99,7 @@ }, { "name": "LWVP", - "link": "_biasLightWorldViewProjectionMatrix", + "link": "_biasLightWorldViewProjectionMatrixSun", "ifndef": ["_CSM"], "ifdef": ["_Sun", "_ShadowMap"] }, diff --git a/Shaders/deferred_light_mobile/deferred_light_mobile.json b/Shaders/deferred_light_mobile/deferred_light_mobile.json index 48774740..4413dc02 100644 --- a/Shaders/deferred_light_mobile/deferred_light_mobile.json +++ b/Shaders/deferred_light_mobile/deferred_light_mobile.json @@ -88,7 +88,7 @@ }, { "name": "LWVP", - "link": "_biasLightWorldViewProjectionMatrix", + "link": "_biasLightWorldViewProjectionMatrixSun", "ifndef": ["_CSM"], "ifdef": ["_Sun", "_ShadowMap"] }, diff --git a/Shaders/volumetric_light/volumetric_light.json b/Shaders/volumetric_light/volumetric_light.json index a8e96a56..dfae1e2c 100755 --- a/Shaders/volumetric_light/volumetric_light.json +++ b/Shaders/volumetric_light/volumetric_light.json @@ -63,7 +63,7 @@ }, { "name": "LWVP", - "link": "_biasLightWorldViewProjectionMatrix", + "link": "_biasLightWorldViewProjectionMatrixSun", "ifndef": ["_CSM"], "ifdef": ["_Sun", "_ShadowMap"] }, diff --git a/blender/arm/material/make_mesh.py b/blender/arm/material/make_mesh.py index 09519dab..00932ef0 100644 --- a/blender/arm/material/make_mesh.py +++ b/blender/arm/material/make_mesh.py @@ -372,7 +372,7 @@ def make_forward_mobile(con_mesh): frag.write('float sdotNL = max(dot(n, sunDir), 0.0);') if is_shadows: vert.add_out('vec4 lightPosition') - vert.add_uniform('mat4 LWVP', '_biasLightWorldViewProjectionMatrix') + vert.add_uniform('mat4 LWVP', '_biasLightWorldViewProjectionMatrixSun') vert.write('lightPosition = LWVP * spos;') frag.add_uniform('bool receiveShadow') frag.add_uniform(f'sampler2DShadow {shadowmap_sun}') @@ -694,7 +694,7 @@ def make_forward_base(con_mesh, parse_opacity=False, transluc_pass=False): vert.write('lightPosition = LVP * vec4(wposition, 1.0);') else: vert.add_out('vec4 lightPosition') - vert.add_uniform('mat4 LWVP', '_biasLightWorldViewProjectionMatrix') + vert.add_uniform('mat4 LWVP', '_biasLightWorldViewProjectionMatrixSun') vert.write('lightPosition = LWVP * spos;') frag.write('vec3 lPos = lightPosition.xyz / lightPosition.w;') frag.write('const vec2 smSize = shadowmapSize;') From ffcc5fcceb6e1c9e12614dccb89e75e513ac47c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Wed, 31 Mar 2021 20:31:31 +0200 Subject: [PATCH 088/264] Make clouds work with Nishita sky model --- blender/arm/make_world.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/blender/arm/make_world.py b/blender/arm/make_world.py index 98ac282c..08eea1ca 100755 --- a/blender/arm/make_world.py +++ b/blender/arm/make_world.py @@ -279,7 +279,12 @@ def frag_write_clouds(world: bpy.types.World, frag: Shader): func_cloud_radiance = 'float cloudRadiance(vec3 p, vec3 dir) {\n' if '_EnvSky' in world.world_defs: - func_cloud_radiance += '\tvec3 sun_dir = hosekSunDirection;\n' + # Nishita sky + if 'vec3 sunDir' in frag.uniforms: + func_cloud_radiance += '\tvec3 sun_dir = sunDir;\n' + # Hosek + else: + func_cloud_radiance += '\tvec3 sun_dir = hosekSunDirection;\n' else: func_cloud_radiance += '\tvec3 sun_dir = vec3(0, 0, -1);\n' func_cloud_radiance += '''\tconst int steps = 8; @@ -313,6 +318,7 @@ def frag_write_clouds(world: bpy.types.World, frag: Shader): \t\tuv += (dir.xy / dir.z) * step_size * cloudsUpper; \t} ''' + if world.arm_darken_clouds: func_trace_clouds += '\t// Darken clouds when the sun is low\n' From c5be90d0b0cdfaaa4543900dbb1b9a236c9338e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Wed, 31 Mar 2021 20:33:52 +0200 Subject: [PATCH 089/264] Cleanup --- Shaders/std/sky.glsl | 44 +++++++------------ Sources/armory/object/Uniforms.hx | 2 +- .../material/cycles_nodes/nodes_texture.py | 2 +- 3 files changed, 17 insertions(+), 31 deletions(-) diff --git a/Shaders/std/sky.glsl b/Shaders/std/sky.glsl index 6fca94dd..d3d04e53 100644 --- a/Shaders/std/sky.glsl +++ b/Shaders/std/sky.glsl @@ -66,25 +66,15 @@ vec3 nishita_lookupLUT(const float height, const float sunTheta) { return textureLod(nishitaLUT, coords, 0.0).rgb; } -/* Approximates the density of ozone for a given sample height. Values taken from Cycles code. */ -float nishita_density_ozone(const float height) { - return (height < 10000.0 || height >= 40000.0) ? 0.0 : (height < 25000.0 ? (height - 10000.0) / 15000.0 : -((height - 40000.0) / 15000.0)); -} - -/* ray-sphere intersection that assumes - * the sphere is centered at the origin. - * No intersection when result.x > result.y */ +/* See raySphereIntersection() in armory/Sources/renderpath/Nishita.hx */ vec2 nishita_rsi(const vec3 r0, const vec3 rd, const float sr) { float a = dot(rd, rd); float b = 2.0 * dot(rd, r0); float c = dot(r0, r0) - (sr * sr); float d = (b*b) - 4.0*a*c; - if (d < 0.0) return vec2(1e5,-1e5); - return vec2( - (-b - sqrt(d))/(2.0*a), - (-b + sqrt(d))/(2.0*a) - ); + // If d < 0.0 the ray does not intersect the sphere + return (d < 0.0) ? vec2(1e5,-1e5) : vec2((-b - sqrt(d))/(2.0*a), (-b + sqrt(d))/(2.0*a)); } /* @@ -94,43 +84,41 @@ vec2 nishita_rsi(const vec3 r0, const vec3 rd, const float sr) { * rPlanet: planet radius */ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const float rPlanet) { - // Calculate the step size of the primary ray. + // Calculate the step size of the primary ray vec2 p = nishita_rsi(r0, r, nishita_atmo_radius); - if (p.x > p.y) return vec3(0,0,0); + if (p.x > p.y) return vec3(0.0); p.y = min(p.y, nishita_rsi(r0, r, rPlanet).x); float iStepSize = (p.y - p.x) / float(nishita_iSteps); - // Initialize the primary ray time. + // Primary ray time float iTime = 0.0; - // Initialize accumulators for Rayleigh and Mie scattering. + // Accumulators for Rayleigh and Mie scattering. vec3 totalRlh = vec3(0,0,0); vec3 totalMie = vec3(0,0,0); - // Initialize optical depth accumulators for the primary ray. + // Optical depth accumulators for the primary ray float iOdRlh = 0.0; float iOdMie = 0.0; - // Calculate the Rayleigh and Mie phases. + // Calculate the Rayleigh and Mie phases float mu = dot(r, pSun); float mumu = mu * mu; float pRlh = 3.0 / (16.0 * PI) * (1.0 + mumu); float pMie = 3.0 / (8.0 * PI) * ((1.0 - nishita_mie_dir_sq) * (mumu + 1.0)) / (pow(1.0 + nishita_mie_dir_sq - 2.0 * mu * nishita_mie_dir, 1.5) * (2.0 + nishita_mie_dir_sq)); - // Sample the primary ray. + // Sample the primary ray for (int i = 0; i < nishita_iSteps; i++) { - // Calculate the primary ray sample position. + // Calculate the primary ray sample position and height vec3 iPos = r0 + r * (iTime + iStepSize * 0.5); - - // Calculate the height of the sample. float iHeight = length(iPos) - rPlanet; // Calculate the optical depth of the Rayleigh and Mie scattering for this step float odStepRlh = exp(-iHeight / nishita_rayleigh_scale) * nishitaDensity.x * iStepSize; float odStepMie = exp(-iHeight / nishita_mie_scale) * nishitaDensity.y * iStepSize; - // Accumulate optical depth. + // Accumulate optical depth iOdRlh += odStepRlh; iOdMie += odStepMie; @@ -142,22 +130,20 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa // Apply dithering to reduce visible banding jODepth += mix(-1000, 1000, random(r.xy)); - // Calculate attenuation. + // Calculate attenuation vec3 attn = exp(-( nishita_mie_coeff * (iOdMie + jODepth.y) + (nishita_rayleigh_coeff) * (iOdRlh + jODepth.x) + nishita_ozone_coeff * jODepth.z )); - // Accumulate scattering. + // Accumulate scattering totalRlh += odStepRlh * attn; totalMie += odStepMie * attn; - // Increment the primary ray time. iTime += iStepSize; } - // Calculate and return the final color. return nishita_sun_intensity * (pRlh * nishita_rayleigh_coeff * totalRlh + pMie * nishita_mie_coeff * totalMie); } @@ -166,7 +152,7 @@ vec3 sun_disk(const vec3 n, const vec3 light_dir, const float disk_size, const f float dist = distance(n, light_dir) / disk_size; // Darken the edges of the sun - // [Hill: 28, 60] (code from [Nec96]) + // [Hill: 28, 60] (according to [Nec96]) float invDist = 1.0 - dist; float mu = sqrt(invDist * invDist); vec3 limb_darkening = 1.0 - (1.0 - pow(vec3(mu), sun_limb_darkening_col)); diff --git a/Sources/armory/object/Uniforms.hx b/Sources/armory/object/Uniforms.hx index 9416e79c..70751dc6 100644 --- a/Sources/armory/object/Uniforms.hx +++ b/Sources/armory/object/Uniforms.hx @@ -177,9 +177,9 @@ class Uniforms { var v: Vec4 = null; switch (link) { case "_nishitaDensity": { - v = iron.object.Uniforms.helpVec; var w = Scene.active.world; if (w != null) { + v = iron.object.Uniforms.helpVec; // We only need Rayleigh and Mie density in the sky shader -> Vec2 v.x = w.raw.nishita_density[0]; v.y = w.raw.nishita_density[1]; diff --git a/blender/arm/material/cycles_nodes/nodes_texture.py b/blender/arm/material/cycles_nodes/nodes_texture.py index f9c35dd8..75e0ffb6 100644 --- a/blender/arm/material/cycles_nodes/nodes_texture.py +++ b/blender/arm/material/cycles_nodes/nodes_texture.py @@ -296,7 +296,7 @@ def parse_tex_sky(node: bpy.types.ShaderNodeTexSky, out_socket: bpy.types.NodeSo if node.sky_type == 'PREETHAM' or node.sky_type == 'HOSEK_WILKIE': if node.sky_type == 'PREETHAM': - log.warn('Preetham sky model is not supported, using Hosek Wilkie sky model instead') + log.info('Info: Preetham sky model is not supported, using Hosek Wilkie sky model instead') return parse_sky_hosekwilkie(node, state) From 656b018e5f73240074f7bbf08514617f6d95f94b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Fri, 2 Apr 2021 01:59:28 +0200 Subject: [PATCH 090/264] Render (ir)radiance probes if no other probes are set --- blender/arm/exporter.py | 11 +-- blender/arm/make_world.py | 63 +++++++++++---- .../material/cycles_nodes/nodes_texture.py | 3 +- blender/arm/write_probes.py | 80 +++++++++++++++++-- 4 files changed, 126 insertions(+), 31 deletions(-) diff --git a/blender/arm/exporter.py b/blender/arm/exporter.py index e3841a28..1c5d9ec3 100755 --- a/blender/arm/exporter.py +++ b/blender/arm/exporter.py @@ -2858,16 +2858,9 @@ class ArmoryExporter: rpdat = arm.utils.get_rp() solid_mat = rpdat.arm_material_model == 'Solid' arm_irradiance = rpdat.arm_irradiance and not solid_mat - arm_radiance = False - radtex = world.arm_envtex_name.rsplit('.', 1)[0] + arm_radiance = rpdat.arm_radiance + radtex = world.arm_envtex_name.rsplit('.', 1)[0] # Remove file extension irrsharmonics = world.arm_envtex_irr_name - - # Radiance - if '_EnvTex' in world.world_defs: - arm_radiance = rpdat.arm_radiance - elif '_EnvSky' in world.world_defs: - arm_radiance = rpdat.arm_radiance - radtex = 'hosek' num_mips = world.arm_envtex_num_mips strength = world.arm_envtex_strength diff --git a/blender/arm/make_world.py b/blender/arm/make_world.py index 08eea1ca..71dac33b 100755 --- a/blender/arm/make_world.py +++ b/blender/arm/make_world.py @@ -17,17 +17,59 @@ shader_datas = [] def build(): + """Builds world shaders for all exported worlds.""" global shader_datas - bpy.data.worlds['Arm'].world_defs = '' + wrd = bpy.data.worlds['Arm'] + rpdat = arm.utils.get_rp() + + mobile_mat = rpdat.arm_material_model == 'Mobile' or rpdat.arm_material_model == 'Solid' + envpath = os.path.join(arm.utils.get_fp_build(), 'compiled', 'Assets', 'envmaps') + + wrd.world_defs = '' worlds = [] shader_datas = [] - for scene in bpy.data.scenes: - # Only export worlds from enabled scenes - if scene.arm_export and scene.world is not None and scene.world not in worlds: - worlds.append(scene.world) - create_world_shaders(scene.world) + with write_probes.setup_envmap_render(): + + for scene in bpy.data.scenes: + world = scene.world + + # Only export worlds from enabled scenes and only once per world + if scene.arm_export and world is not None and world not in worlds: + worlds.append(world) + + world.arm_envtex_name = '' + create_world_shaders(world) + + if rpdat.arm_irradiance: + # Plain background color + if '_EnvCol' in world.world_defs: + world_name = arm.utils.safestr(world.name) + # Irradiance json file name + world.arm_envtex_name = world_name + world.arm_envtex_irr_name = world_name + write_probes.write_color_irradiance(world_name, world.arm_envtex_color) + + # Render world to envmap for (ir)radiance, if no + # other probes are exported + elif world.arm_envtex_name == '': + write_probes.render_envmap(envpath, world) + + filename = f'env_{arm.utils.safesrc(world.name)}' + image_file = f'{filename}.jpg' + image_filepath = os.path.join(envpath, image_file) + + world.arm_envtex_name = image_file + world.arm_envtex_irr_name = os.path.basename(image_filepath).rsplit('.', 1)[0] + + write_radiance = rpdat.arm_radiance and not mobile_mat + mip_count = write_probes.write_probes(image_filepath, True, world.arm_envtex_num_mips, write_radiance) + world.arm_envtex_num_mips = mip_count + + if write_radiance: + # Set world def, everything else is handled by write_probes() + wrd.world_defs += '_Rad' def create_world_shaders(world: bpy.types.World): @@ -131,14 +173,7 @@ def build_node_tree(world: bpy.types.World, frag: Shader, vert: Shader, con: Sha col = world.color world.arm_envtex_color = [col[0], col[1], col[2], 1.0] world.arm_envtex_strength = 1.0 - - # Irradiance/Radiance: clear to color if no texture or sky is provided - if rpdat.arm_irradiance or rpdat.arm_irradiance: - if '_EnvSky' not in world.world_defs and '_EnvTex' not in world.world_defs and '_EnvImg' not in world.world_defs: - # Irradiance json file name - world.arm_envtex_name = world_name - world.arm_envtex_irr_name = world_name - write_probes.write_color_irradiance(world_name, world.arm_envtex_color) + world.world_defs += '_EnvCol' # Clouds enabled if rpdat.arm_clouds and world.arm_use_clouds: diff --git a/blender/arm/material/cycles_nodes/nodes_texture.py b/blender/arm/material/cycles_nodes/nodes_texture.py index 75e0ffb6..516d555b 100644 --- a/blender/arm/material/cycles_nodes/nodes_texture.py +++ b/blender/arm/material/cycles_nodes/nodes_texture.py @@ -294,6 +294,8 @@ def parse_tex_sky(node: bpy.types.ShaderNodeTexSky, out_socket: bpy.types.NodeSo # Pass through return c.to_vec3([0.0, 0.0, 0.0]) + state.world.world_defs += '_EnvSky' + if node.sky_type == 'PREETHAM' or node.sky_type == 'HOSEK_WILKIE': if node.sky_type == 'PREETHAM': log.info('Info: Preetham sky model is not supported, using Hosek Wilkie sky model instead') @@ -315,7 +317,6 @@ def parse_sky_hosekwilkie(node: bpy.types.ShaderNodeTexSky, state: ParserState) # Match to cycles world.arm_envtex_strength *= 0.1 - world.world_defs += '_EnvSky' assets.add_khafile_def('arm_hosek') curshader.add_uniform('vec3 A', link="_hosekA") curshader.add_uniform('vec3 B', link="_hosekB") diff --git a/blender/arm/write_probes.py b/blender/arm/write_probes.py index 11529394..608deab5 100644 --- a/blender/arm/write_probes.py +++ b/blender/arm/write_probes.py @@ -1,23 +1,89 @@ -import bpy +from contextlib import contextmanager +import math import multiprocessing import os -import sys -import subprocess -import json import re -import arm.utils +import subprocess + +import bpy + import arm.assets as assets +import arm.log as log +import arm.utils + def add_irr_assets(output_file_irr): assets.add(output_file_irr + '.arm') + def add_rad_assets(output_file_rad, rad_format, num_mips): assets.add(output_file_rad + '.' + rad_format) for i in range(0, num_mips): assets.add(output_file_rad + '_' + str(i) + '.' + rad_format) -# Generate probes from environment map -def write_probes(image_filepath, disable_hdr, cached_num_mips, arm_radiance=True): + +@contextmanager +def setup_envmap_render(): + """Creates a background scene for rendering environment textures. + Use it as a context manager to automatically clean up on errors. + """ + rpdat = arm.utils.get_rp() + radiance_size = int(rpdat.arm_radiance_size) + + # TODO: Add world option to render .hdr in the UI + + # Render worlds in a different scene so that there are no other + # objects. The actual scene might be called differently if the name + # is already taken + scene = bpy.data.scenes.new("_arm_envmap_render") + scene.render.engine = "CYCLES" + scene.render.image_settings.file_format = "JPEG" + scene.render.image_settings.quality = 100 + scene.render.resolution_x = radiance_size + scene.render.resolution_y = radiance_size // 2 + + # Set GPU as rendering device if the user enabled it + if bpy.context.preferences.addons["cycles"].preferences.compute_device_type == "CUDA": + scene.cycles.device = "GPU" + else: + log.info('Armory: Using CPU for environment render (might be slow). Enable CUDA if possible.') + + # One sample is enough for world background only + scene.cycles.samples = 1 + + # Setup scene + cam = bpy.data.cameras.new("_arm_cam_envmap_render") + cam_obj = bpy.data.objects.new("_arm_cam_envmap_render", cam) + scene.collection.objects.link(cam_obj) + scene.camera = cam_obj + + cam_obj.location = [0.0, 0.0, 0.0] + cam.type = "PANO" + cam.cycles.panorama_type = "EQUIRECTANGULAR" + cam_obj.rotation_euler = [math.radians(90), 0, math.radians(-90)] + + try: + yield + finally: + bpy.data.objects.remove(cam_obj) + bpy.data.cameras.remove(cam) + bpy.data.scenes.remove(scene) + + +def render_envmap(target_dir: str, world: bpy.types.World): + """Renders an environment texture for the given world into the + target_dir. Use in combination with setup_envmap_render().""" + scene = bpy.data.scenes["_arm_envmap_render"] + scene.world = world + + render_path = os.path.join(target_dir, f"env_{arm.utils.safesrc(world.name)}.jpg") + scene.render.filepath = render_path + + bpy.ops.render.render(write_still=True, scene=scene.name) + + +def write_probes(image_filepath: str, disable_hdr: bool, cached_num_mips: int, arm_radiance=True) -> int: + """Generate probes from environment map and returns the mipmap count""" envpath = arm.utils.get_fp_build() + '/compiled/Assets/envmaps' if not os.path.exists(envpath): From 34f08e6922d47978ce8ab3d83936a3ae84a7776a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Fri, 2 Apr 2021 01:59:55 +0200 Subject: [PATCH 091/264] Fix irradiance brightness See write_sky_irradiance() for reference --- blender/arm/write_probes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/blender/arm/write_probes.py b/blender/arm/write_probes.py index 608deab5..496981f1 100644 --- a/blender/arm/write_probes.py +++ b/blender/arm/write_probes.py @@ -317,6 +317,8 @@ def sh_to_json(sh_file): parse_band_floats(irradiance_floats, band0_line) parse_band_floats(irradiance_floats, band1_line) parse_band_floats(irradiance_floats, band2_line) + for i in range(0, len(irradiance_floats)): + irradiance_floats[i] /= 2 sh_json = {'irradiance': irradiance_floats} ext = '.arm' if bpy.data.worlds['Arm'].arm_minimize else '' From b930b87bc955eb20d69c7922f4c36c85055b1e19 Mon Sep 17 00:00:00 2001 From: Lubos Lenco Date: Fri, 2 Apr 2021 13:34:08 +0200 Subject: [PATCH 092/264] Bump version --- blender/arm/props.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/props.py b/blender/arm/props.py index 91fe1ccf..5143c8c6 100755 --- a/blender/arm/props.py +++ b/blender/arm/props.py @@ -11,7 +11,7 @@ import arm.proxy import arm.utils # Armory version -arm_version = '2021.3' +arm_version = '2021.4' arm_commit = '$Id$' def get_project_html5_copy(self): From 899411dea4005594caa42b9f62faaa76c1e63d8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Fri, 2 Apr 2021 02:18:11 +0200 Subject: [PATCH 093/264] Cleanup write_probes.py --- blender/arm/write_probes.py | 79 +++++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/blender/arm/write_probes.py b/blender/arm/write_probes.py index 496981f1..f6ca036e 100644 --- a/blender/arm/write_probes.py +++ b/blender/arm/write_probes.py @@ -30,8 +30,6 @@ def setup_envmap_render(): rpdat = arm.utils.get_rp() radiance_size = int(rpdat.arm_radiance_size) - # TODO: Add world option to render .hdr in the UI - # Render worlds in a different scene so that there are no other # objects. The actual scene might be called differently if the name # is already taken @@ -129,7 +127,7 @@ def write_probes(image_filepath: str, disable_hdr: bool, cached_num_mips: int, a scaled_file = output_file_rad + '.' + rad_format if arm.utils.get_os() == 'win': - output = subprocess.check_output([ \ + subprocess.check_output([ kraffiti_path, 'from=' + input_file, 'to=' + scaled_file, @@ -137,35 +135,35 @@ def write_probes(image_filepath: str, disable_hdr: bool, cached_num_mips: int, a 'width=' + str(target_w), 'height=' + str(target_h)]) else: - output = subprocess.check_output([ \ - kraffiti_path + \ - ' from="' + input_file + '"' + \ - ' to="' + scaled_file + '"' + \ - ' format=' + rad_format + \ - ' width=' + str(target_w) + \ - ' height=' + str(target_h)], shell=True) + subprocess.check_output([ + kraffiti_path + + ' from="' + input_file + '"' + + ' to="' + scaled_file + '"' + + ' format=' + rad_format + + ' width=' + str(target_w) + + ' height=' + str(target_h)], shell=True) # Irradiance spherical harmonics if arm.utils.get_os() == 'win': - subprocess.call([ \ + subprocess.call([ cmft_path, '--input', scaled_file, '--filter', 'shcoeffs', '--outputNum', '1', '--output0', output_file_irr]) else: - subprocess.call([ \ - cmft_path + \ - ' --input ' + '"' + scaled_file + '"' + \ - ' --filter shcoeffs' + \ - ' --outputNum 1' + \ - ' --output0 ' + '"' + output_file_irr + '"'], shell=True) + subprocess.call([ + cmft_path + + ' --input ' + '"' + scaled_file + '"' + + ' --filter shcoeffs' + + ' --outputNum 1' + + ' --output0 ' + '"' + output_file_irr + '"'], shell=True) sh_to_json(output_file_irr) add_irr_assets(output_file_irr) # Mip-mapped radiance - if arm_radiance == False: + if not arm_radiance: return cached_num_mips # 4096 = 256 face @@ -266,37 +264,37 @@ def write_probes(image_filepath: str, disable_hdr: bool, cached_num_mips: int, a if disable_hdr is True: for f in generated_files: if arm.utils.get_os() == 'win': - subprocess.call([ \ + subprocess.call([ kraffiti_path, 'from=' + f + '.hdr', 'to=' + f + '.jpg', 'format=jpg']) else: - subprocess.call([ \ - kraffiti_path + \ - ' from="' + f + '.hdr"' + \ - ' to="' + f + '.jpg"' + \ - ' format=jpg'], shell=True) + subprocess.call([ + kraffiti_path + + ' from="' + f + '.hdr"' + + ' to="' + f + '.jpg"' + + ' format=jpg'], shell=True) os.remove(f + '.hdr') # Scale from (4x2 to 1x1> - for i in range (0, 2): + for i in range(0, 2): last = generated_files[-1] out = output_file_rad + '_' + str(mip_count + i) if arm.utils.get_os() == 'win': - subprocess.call([ \ + subprocess.call([ kraffiti_path, 'from=' + last + '.' + rad_format, 'to=' + out + '.' + rad_format, 'scale=0.5', 'format=' + rad_format], shell=True) else: - subprocess.call([ \ - kraffiti_path + \ - ' from=' + '"' + last + '.' + rad_format + '"' + \ - ' to=' + '"' + out + '.' + rad_format + '"' + \ - ' scale=0.5' + \ - ' format=' + rad_format], shell=True) + subprocess.call([ + kraffiti_path + + ' from=' + '"' + last + '.' + rad_format + '"' + + ' to=' + '"' + out + '.' + rad_format + '"' + + ' scale=0.5' + + ' format=' + rad_format], shell=True) generated_files.append(out) mip_count += 2 @@ -305,6 +303,7 @@ def write_probes(image_filepath: str, disable_hdr: bool, cached_num_mips: int, a return mip_count + def sh_to_json(sh_file): """Parse sh coefs produced by cmft into json array""" with open(sh_file + '.c') as f: @@ -327,15 +326,26 @@ def sh_to_json(sh_file): # Clean up .c os.remove(sh_file + '.c') + def parse_band_floats(irradiance_floats, band_line): string_floats = re.findall(r'[-+]?\d*\.\d+|\d+', band_line) - string_floats = string_floats[1:] # Remove 'Band 0/1/2' number + string_floats = string_floats[1:] # Remove 'Band 0/1/2' number for s in string_floats: irradiance_floats.append(float(s)) + def write_sky_irradiance(base_name): # Hosek spherical harmonics - irradiance_floats = [1.5519331988822218,2.3352207154503266,2.997277451988076,0.2673894962434794,0.4305630474135794,0.11331825259716752,-0.04453633521758638,-0.038753175134160295,-0.021302768541875794,0.00055858020486499,0.000371654770334503,0.000126606145406403,-0.000135708721978705,-0.000787399554583089,-0.001550090690860059,0.021947399048903773,0.05453650591711572,0.08783641266630278,0.17053593578630663,0.14734127083304463,0.07775404698816404,-2.6924363189795e-05,-7.9350169701934e-05,-7.559914435231e-05,0.27035455385870993,0.23122918445556914,0.12158817295211832] + irradiance_floats = [ + 1.5519331988822218, 2.3352207154503266, 2.997277451988076, + 0.2673894962434794, 0.4305630474135794, 0.11331825259716752, + -0.04453633521758638, -0.038753175134160295, -0.021302768541875794, + 0.00055858020486499, 0.000371654770334503, 0.000126606145406403, + -0.000135708721978705, -0.000787399554583089, -0.001550090690860059, + 0.021947399048903773, 0.05453650591711572, 0.08783641266630278, + 0.17053593578630663, 0.14734127083304463, 0.07775404698816404, + -2.6924363189795e-05, -7.9350169701934e-05, -7.559914435231e-05, + 0.27035455385870993, 0.23122918445556914, 0.12158817295211832] for i in range(0, len(irradiance_floats)): irradiance_floats[i] /= 2 @@ -350,6 +360,7 @@ def write_sky_irradiance(base_name): assets.add(output_file + '.arm') + def write_color_irradiance(base_name, col): """Constant color irradiance""" # Adjust to Cycles From 766e26a7df8ee9ed382076afcc76da07f7be9055 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Sat, 3 Apr 2021 20:48:21 -0300 Subject: [PATCH 094/264] Expose collision filter mask as integer to the UI --- blender/arm/props_collision_filter_mask.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/blender/arm/props_collision_filter_mask.py b/blender/arm/props_collision_filter_mask.py index 00086ac8..e2a4d803 100644 --- a/blender/arm/props_collision_filter_mask.py +++ b/blender/arm/props_collision_filter_mask.py @@ -22,6 +22,13 @@ class ARM_PT_RbCollisionFilterMaskPanel(bpy.types.Panel): layout.use_property_decorate = False obj = context.object layout.prop(obj, 'arm_rb_collision_filter_mask', text="", expand=True) + col_mask = '' + for b in obj.arm_rb_collision_filter_mask: + col_mask = ('1' if b else '0') + col_mask + col = layout.column() + row = col.row() + row.alignment = 'RIGHT' + row.label(text=f'Integer Mask Value: {str(int(col_mask, 2))}') def register(): bpy.utils.register_class(ARM_PT_RbCollisionFilterMaskPanel) From 614385ea7b7af6abef024b207ffe89f746f1b9ed Mon Sep 17 00:00:00 2001 From: N8n5h Date: Wed, 7 Apr 2021 09:40:41 -0300 Subject: [PATCH 095/264] Setup callbacks so tiles are notified when a light is removed A secondary callback is setup so the tile it's also removed from the activeTiles, because it's freed in a "non standard" way. --- Sources/armory/renderpath/Inc.hx | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Sources/armory/renderpath/Inc.hx b/Sources/armory/renderpath/Inc.hx index 62109321..28573879 100644 --- a/Sources/armory/renderpath/Inc.hx +++ b/Sources/armory/renderpath/Inc.hx @@ -583,6 +583,11 @@ class ShadowMapAtlas { return false; // push main tile to active tiles atlas.activeTiles.push(mainTile); + // notify the tile on light remove + light.tileNotifyOnRemove = mainTile.notifyOnLightRemove; + // notify atlas when this tile is freed + mainTile.notifyOnFree = atlas.freeActiveTile; + return true; } @@ -731,6 +736,10 @@ class ShadowMapAtlas { } #end } + + function freeActiveTile(tile: ShadowMapTile) { + activeTiles.remove(tile); + } } class ShadowMapTile { @@ -950,6 +959,11 @@ class ShadowMapTile { } } + public function notifyOnLightRemove() { + unlockLight = true; + freeTile(); + } + inline function lockTile(light: LightObject): Void { if (this.light != null) return; @@ -963,6 +977,7 @@ class ShadowMapTile { } public var unlockLight: Bool = false; + public var notifyOnFree: ShadowMapTile -> Void; public function freeTile(): Void { // prevent duplicates @@ -988,6 +1003,11 @@ class ShadowMapTile { tempTile.linkedTile = null; tempTile = linkedTile; } + // notify atlas that this tile has been freed + if (notifyOnFree != null) { + notifyOnFree(this); + notifyOnFree = null; + } } public inline function forEachTileLinked(action: ShadowMapTile->Void): Void { From 63716eea17ba6736f0639927acfdcab6b765d9d3 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Wed, 7 Apr 2021 10:02:12 -0300 Subject: [PATCH 096/264] Modifications around addLight from Inc * Removed redundant checks at the start of the function. * changed the return value to void because the "return false" were redundant, and simply made it so the lightInAtlas is set at the end of the function. * Removed unused variable --- Sources/armory/renderpath/Inc.hx | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/Sources/armory/renderpath/Inc.hx b/Sources/armory/renderpath/Inc.hx index 28573879..cdf500a9 100644 --- a/Sources/armory/renderpath/Inc.hx +++ b/Sources/armory/renderpath/Inc.hx @@ -115,7 +115,7 @@ class Inc { for (light in iron.Scene.active.lights) { if (!light.lightInAtlas && !light.culledLight && light.visible && light.shadowMapScale > 0.0 && light.data.raw.strength > 0.0 && light.data.raw.cast_shadow) { - light.lightInAtlas = ShadowMapAtlas.addLight(light); + ShadowMapAtlas.addLight(light); } } // update point light data before rendering @@ -564,11 +564,7 @@ class ShadowMapAtlas { * @param light of type LightObject to be added to an yatlas * @return if the light was added succesfully */ - public static function addLight(light: LightObject): Bool { - // check if light can be added based on culling - if (light.culledLight || light.shadowMapScale == 0.0) - return false; - + public static function addLight(light: LightObject) { var atlasName = shadowMapAtlasName(light.data.raw.type); var atlas = shadowMapAtlases.get(atlasName); if (atlas == null) { @@ -580,15 +576,15 @@ class ShadowMapAtlas { // find a free tile for this light var mainTile = ShadowMapTile.assignTiles(light, atlas, null); if (mainTile == null) - return false; - // push main tile to active tiles + return; + atlas.activeTiles.push(mainTile); // notify the tile on light remove light.tileNotifyOnRemove = mainTile.notifyOnLightRemove; // notify atlas when this tile is freed mainTile.notifyOnFree = atlas.freeActiveTile; - - return true; + // "lock" light to make sure it's not eligible to be added again + light.lightInAtlas = true; } static inline function shadowMapAtlasSize(light:LightObject):Int { @@ -805,7 +801,6 @@ class ShadowMapTile { static inline function findCreateTiles(light: LightObject, oldTile: ShadowMapTile, atlas: ShadowMapAtlas, tilesPerLightType: Int, tileSize: Int): Array { var tilesFound: Array = []; - var updateAtlas = false; while (tilesFound.length < tilesPerLightType) { findTiles(light, oldTile, atlas.tiles, tileSize, tilesPerLightType, tilesFound); From cf56c4f1d8a73bcdb93102b722c0986c4cd8c610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 8 Apr 2021 15:44:18 +0200 Subject: [PATCH 097/264] Fix GetSystemName node --- Sources/armory/logicnode/GetSystemName.hx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Sources/armory/logicnode/GetSystemName.hx b/Sources/armory/logicnode/GetSystemName.hx index a346f01c..97be58b6 100644 --- a/Sources/armory/logicnode/GetSystemName.hx +++ b/Sources/armory/logicnode/GetSystemName.hx @@ -3,22 +3,24 @@ package armory.logicnode; class GetSystemName extends LogicNode { public function new(tree: LogicTree) { - super(tree); + super(tree); } - public static function equalsCI(a : String, b : String) return a.toLowerCase() == b.toLowerCase(); - override function get(from: Int): Dynamic { + var systemName: String = kha.System.systemId; return switch (from) { case 0: systemName; - case 1: equalsCI(kha.System.systemId, 'Windows'); - case 2: equalsCI(kha.System.systemId, 'Linux'); - case 3: equalsCI(kha.System.systemId, 'Mac'); - case 4: equalsCI(kha.System.systemId, 'HTML5'); - case 5: equalsCI(kha.System.systemId, 'Android'); + case 1: equalsCI(systemName, 'Windows'); + case 2: equalsCI(systemName, 'Linux'); + case 3: equalsCI(systemName, 'Mac'); + case 4: equalsCI(systemName, 'HTML5'); + case 5: equalsCI(systemName, 'Android'); default: null; } + } + static inline function equalsCI(a: String, b: String): Bool { + return a.toLowerCase() == b.toLowerCase(); } } From 78e266f77b63070ebf7065c114472207c774c0de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Fri, 9 Apr 2021 20:55:17 +0200 Subject: [PATCH 098/264] Improve warning message for unsupported/unknown fcurve data paths --- blender/arm/exporter.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/blender/arm/exporter.py b/blender/arm/exporter.py index 90dc67ac..33459a62 100755 --- a/blender/arm/exporter.py +++ b/blender/arm/exporter.py @@ -279,6 +279,8 @@ class ArmoryExporter: return out_track def export_object_transform(self, bobject: bpy.types.Object, o): + wrd = bpy.data.worlds['Arm'] + # Static transform o['transform'] = {'values': ArmoryExporter.write_matrix(bobject.matrix_local)} @@ -292,7 +294,7 @@ class ArmoryExporter: fp = self.get_meshes_file_path('action_' + action_name, compressed=ArmoryExporter.compress_enabled) assets.add(fp) ext = '.lz4' if ArmoryExporter.compress_enabled else '' - if ext == '' and not bpy.data.worlds['Arm'].arm_minimize: + if ext == '' and not wrd.arm_minimize: ext = '.json' if 'object_actions' not in o: @@ -308,6 +310,7 @@ class ArmoryExporter: self.export_pose_markers(out_anim, action) + unresolved_data_paths = set() for fcurve in action.fcurves: data_path = fcurve.data_path @@ -315,7 +318,11 @@ class ArmoryExporter: out_track = self.export_animation_track(fcurve, frame_range, FCURVE_TARGET_NAMES[data_path][fcurve.array_index]) except KeyError: if data_path not in FCURVE_TARGET_NAMES: - log.warn(f"Action {action_name}: The data path '{data_path}' is not supported (yet)!") + # This can happen if the target is simply not + # supported or the action shares both bone + # and object transform data (FCURVE_TARGET_NAMES + # only contains object transform targets) + unresolved_data_paths.add(data_path) continue # Missing target entry for array_index or something else else: @@ -323,8 +330,20 @@ class ArmoryExporter: out_anim['tracks'].append(out_track) + if len(unresolved_data_paths) > 0: + warning = ( + f'The action "{action_name}" has fcurve channels with data paths that could not be resolved.' + ' This can be caused by the following things:\n' + ' - The data paths are not supported.\n' + ' - The action exists on both armature and non-armature objects or has both bone and object transform data.' + ) + if wrd.arm_verbose_output: + warning += f'\n Unresolved data paths: {unresolved_data_paths}' + else: + warning += '\n To see the list of unresolved data paths please recompile with Armory Project > Verbose Output enabled.' + log.warn(warning) + if True: # not action.arm_cached or not os.path.exists(fp): - wrd = bpy.data.worlds['Arm'] if wrd.arm_verbose_output: print('Exporting object action ' + action_name) From e1cfa2f1da974010085c8621e171ce70a81f6758 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Wed, 14 Apr 2021 00:31:34 -0300 Subject: [PATCH 099/264] Add shadow map atlases info to the debug console Display information like tile size, current size, max size, free tiles, etc. to help debug atlases. --- Sources/armory/renderpath/Inc.hx | 26 +++++- Sources/armory/trait/internal/DebugConsole.hx | 85 +++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/Sources/armory/renderpath/Inc.hx b/Sources/armory/renderpath/Inc.hx index cdf500a9..934da96f 100644 --- a/Sources/armory/renderpath/Inc.hx +++ b/Sources/armory/renderpath/Inc.hx @@ -112,6 +112,12 @@ class Inc { lastFrame = RenderPath.active.frame; #end // add new lights to the atlases + #if arm_debug + // reset data on rejected lights + for (atlas in ShadowMapAtlas.shadowMapAtlases) { + atlas.rejectedLights = []; + } + #end for (light in iron.Scene.active.lights) { if (!light.lightInAtlas && !light.culledLight && light.visible && light.shadowMapScale > 0.0 && light.data.raw.strength > 0.0 && light.data.raw.cast_shadow) { @@ -544,6 +550,11 @@ class ShadowMapAtlas { public var updateRenderTarget = false; public static var shadowMapAtlases:Map = new Map(); // map a shadowmap atlas to their light type + #if arm_debug + public var lightType: String; + public var rejectedLights: Array = []; + #end + function new(light: LightObject) { var maxTileSize = shadowMapAtlasSize(light); @@ -557,6 +568,14 @@ class ShadowMapAtlas { computeTileSizes(maxTileSize, depth); #end + #if arm_debug + #if arm_shadowmap_atlas_single_map + this.lightType = "any"; + #else + this.lightType = light.data.raw.type; + #end + #end + } /** @@ -575,8 +594,13 @@ class ShadowMapAtlas { // find a free tile for this light var mainTile = ShadowMapTile.assignTiles(light, atlas, null); - if (mainTile == null) + if (mainTile == null) { + #if arm_debug + if (!atlas.rejectedLights.contains(light)) + atlas.rejectedLights.push(light); + #end return; + } atlas.activeTiles.push(mainTile); // notify the tile on light remove diff --git a/Sources/armory/trait/internal/DebugConsole.hx b/Sources/armory/trait/internal/DebugConsole.hx index 03096bb3..2e1c3db3 100755 --- a/Sources/armory/trait/internal/DebugConsole.hx +++ b/Sources/armory/trait/internal/DebugConsole.hx @@ -8,6 +8,10 @@ import iron.object.MeshObject; import zui.Zui; import zui.Id; using armory.object.TransformExtension; +#if arm_shadowmap_atlas +import armory.renderpath.Inc.ShadowMapTile; +import armory.renderpath.Inc.ShadowMapAtlas; +#end #end #if arm_debug @@ -423,6 +427,7 @@ class DebugConsole extends Trait { light.data.raw.strength = ui.slider(lightHandle, "Strength", 0.0, 5.0, true) * 10; #if arm_shadowmap_atlas ui.text("status: " + (light.culledLight ? "culled" : "rendered")); + ui.text("shadow map size: " + light.data.raw.shadowmap_size); #end } else if (Std.is(selectedObject, iron.object.CameraObject)) { @@ -520,6 +525,86 @@ class DebugConsole extends Trait { ui.unindent(); } + #if arm_shadowmap_atlas + if (ui.panel(Id.handle({selected: false}), "Shadow Map Atlases")) { + inline function highLightNext() { + ui.g.color = kha.Color.fromFloats(0.175, 0.175, 0.175); + ui.g.fillRect(ui._x, ui._y, ui._windowW, ui.ELEMENT_H()); + ui.g.color = 0xffffffff; + } + ui.indent(false); + ui.text("Constants:"); + highLightNext(); + ui.text('Tiles Used Per Point Light: ${ ShadowMapTile.tilesLightType("point") }'); + ui.text('Tiles Used Per Spot Light: ${ ShadowMapTile.tilesLightType("spot") }'); + highLightNext(); + ui.text('Tiles Used For Sun: ${ ShadowMapTile.tilesLightType("sun") }'); + ui.unindent(false); + ui.indent(false); + var i = 0; + for (atlas in ShadowMapAtlas.shadowMapAtlases) { + if (ui.panel(Id.handle({selected: false}).nest(i), atlas.target )) { + ui.indent(false); + // general atlas information + ui.text('Current Size: ${atlas.sizew}, ${atlas.sizeh} px'); + highLightNext(); + ui.text('Max Size: ${atlas.maxAtlasSizeConst}, ${atlas.maxAtlasSizeConst} px'); + ui.text('Tile Size: ${atlas.baseTileSizeConst}, ${atlas.baseTileSizeConst} px'); + highLightNext(); + #if arm_shadowmap_atlas_lod + // show detailed information per light + if (ui.panel(Id.handle({selected: false}).nest(i).nest(0), )) { + ui.indent(false); + var j = 1; + for (tile in atlas.activeTiles) { + if (ui.panel(Id.handle({selected: false}).nest(i).nest(j), tile.light.name)) { + ui.indent(false); + ui.text('Shadow Map Size: ${tile.size}, ${tile.size} px'); + ui.unindent(); + } + j++; + } + ui.unindent(false); + } + #else + // show total lights occupied + ui.text('Current Active Lights: ${atlas.activeTiles.length}'); + #end + + #if arm_shadowmap_atlas_lod + // WIP + #else + var unusedTiles = atlas.tiles.length; + #if arm_shadowmap_atlas_single_map + for (tile in atlas.activeTiles) + unusedTiles -= ShadowMapTile.tilesLightType(tile.light.data.raw.type); + #else + unusedTiles -= atlas.activeTiles.length * ShadowMapTile.tilesLightType(atlas.lightType); + #end + ui.text('Unused tiles: ${unusedTiles}'); + #end + + var rejectedLightsNames = ""; + if (atlas.rejectedLights.length > 0) { + for (l in atlas.rejectedLights) + rejectedLightsNames += l.name + ", "; + rejectedLightsNames = rejectedLightsNames.substr(0, rejectedLightsNames.length - 2); + highLightNext(); + ui.text('Not enough space in atlas for ${atlas.rejectedLights.length} light${atlas.rejectedLights.length > 1 ? "s" : ""}:'); + ui.indent(); + ui.text(${rejectedLightsNames}); + ui.unindent(false); + } + + ui.unindent(false); + } + i++; + } + ui.unindent(false); + ui.unindent(false); + } + #end + if (ui.panel(Id.handle({selected: false}), "Render Targets")) { ui.indent(); #if (kha_opengl || kha_webgl) From c01b079cf14ec414983ade9e716c60f4aa3c0522 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Wed, 14 Apr 2021 01:06:09 -0300 Subject: [PATCH 100/264] Add custom highlighting for debug console And use it to highlight "not enough lights" with a different color to bring attention to it. --- Sources/armory/trait/internal/DebugConsole.hx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/armory/trait/internal/DebugConsole.hx b/Sources/armory/trait/internal/DebugConsole.hx index 2e1c3db3..adf70b1f 100755 --- a/Sources/armory/trait/internal/DebugConsole.hx +++ b/Sources/armory/trait/internal/DebugConsole.hx @@ -527,8 +527,8 @@ class DebugConsole extends Trait { #if arm_shadowmap_atlas if (ui.panel(Id.handle({selected: false}), "Shadow Map Atlases")) { - inline function highLightNext() { - ui.g.color = kha.Color.fromFloats(0.175, 0.175, 0.175); + inline function highLightNext(color: kha.Color = null) { + ui.g.color = color != null ? color : kha.Color.fromFloats(0.175, 0.175, 0.175); ui.g.fillRect(ui._x, ui._y, ui._windowW, ui.ELEMENT_H()); ui.g.color = 0xffffffff; } @@ -589,7 +589,7 @@ class DebugConsole extends Trait { for (l in atlas.rejectedLights) rejectedLightsNames += l.name + ", "; rejectedLightsNames = rejectedLightsNames.substr(0, rejectedLightsNames.length - 2); - highLightNext(); + highLightNext(kha.Color.fromFloats(0.447, 0.247, 0.188)); ui.text('Not enough space in atlas for ${atlas.rejectedLights.length} light${atlas.rejectedLights.length > 1 ? "s" : ""}:'); ui.indent(); ui.text(${rejectedLightsNames}); From da41d1fb170dcb58f938db1895eec8800029a3e5 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Fri, 16 Apr 2021 11:43:19 -0300 Subject: [PATCH 101/264] Fix compile error with debug and shadow map atlas LOD --- Sources/armory/trait/internal/DebugConsole.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/armory/trait/internal/DebugConsole.hx b/Sources/armory/trait/internal/DebugConsole.hx index adf70b1f..86aee9fb 100755 --- a/Sources/armory/trait/internal/DebugConsole.hx +++ b/Sources/armory/trait/internal/DebugConsole.hx @@ -553,7 +553,7 @@ class DebugConsole extends Trait { highLightNext(); #if arm_shadowmap_atlas_lod // show detailed information per light - if (ui.panel(Id.handle({selected: false}).nest(i).nest(0), )) { + if (ui.panel(Id.handle({selected: false}).nest(i).nest(0), "Lights")) { ui.indent(false); var j = 1; for (tile in atlas.activeTiles) { From c21d17d04e2427a1331019a2caef37dfd524cc3d Mon Sep 17 00:00:00 2001 From: N8n5h Date: Sun, 18 Apr 2021 10:25:27 -0300 Subject: [PATCH 102/264] Add visual representation of shadow map atlases to the debug console This is done to make it easier to understand what is going on with atlases to help users and also the maintenance of the feature. Max atlas size and max tile size where moved to the graph to make it more understandable. --- Sources/armory/trait/internal/DebugConsole.hx | 129 ++++++++++++++++-- 1 file changed, 115 insertions(+), 14 deletions(-) diff --git a/Sources/armory/trait/internal/DebugConsole.hx b/Sources/armory/trait/internal/DebugConsole.hx index 86aee9fb..483de91f 100755 --- a/Sources/armory/trait/internal/DebugConsole.hx +++ b/Sources/armory/trait/internal/DebugConsole.hx @@ -65,6 +65,11 @@ class DebugConsole extends Trait { var shortcut_scale_in = kha.input.KeyCode.OpenBracket; var shortcut_scale_out = kha.input.KeyCode.CloseBracket; + #if arm_shadowmap_atlas + var lightColorMap: Map = new Map(); + var lightColorMapCount = 0; + #end + public function new(scaleFactor = 1.0, scaleDebugConsole = 1.0, positionDebugConsole = 2, visibleDebugConsole = 1, keyCodeVisible = kha.input.KeyCode.Tilde, keyCodeScaleIn = kha.input.KeyCode.OpenBracket, keyCodeScaleOut = kha.input.KeyCode.CloseBracket) { super(); @@ -528,10 +533,78 @@ class DebugConsole extends Trait { #if arm_shadowmap_atlas if (ui.panel(Id.handle({selected: false}), "Shadow Map Atlases")) { inline function highLightNext(color: kha.Color = null) { - ui.g.color = color != null ? color : kha.Color.fromFloats(0.175, 0.175, 0.175); + ui.g.color = color != null ? color : -13882324; ui.g.fillRect(ui._x, ui._y, ui._windowW, ui.ELEMENT_H()); ui.g.color = 0xffffffff; } + + inline function drawScale(text: String, y: Float, fromX: Float, toX: Float, bottom = false) { + var _off = bottom ? -4 : 4; + ui.g.drawLine(fromX, y, toX, y); + ui.g.drawLine(fromX, y, fromX, y + _off); + ui.g.drawLine(toX, y, toX, y + _off); + + var _w = ui._w; + ui._w = Std.int(Math.abs(toX - fromX)); + ui.text(text, Align.Center); + ui._w = _w; + } + + /** + * create a kha Color from HSV (Hue, Saturation, Value) + * @param h expected Hue from [0, 1]. + * @param s expected Saturation from [0, 1]. + * @param v expected Value from [0, 1]. + * @return kha.Color + */ + function colorFromHSV(h: Float, s: Float, v: Float): kha.Color { + // https://stackoverflow.com/a/17243070 + var r = 0.0; var g = 0.0; var b = 0.0; + + var i = Math.floor(h * 6); + var f = h * 6 - i; + var p = v * (1 - s); + var q = v * (1 - f * s); + var t = v * (1 - (1 - f) * s); + + switch (i % 6) { + case 0: { r = v; g = t; b = p; } + case 1: { r = q; g = v; b = p; } + case 2: { r = p; g = v; b = t; } + case 3: { r = p; g = q; b = v; } + case 4: { r = t; g = p; b = v; } + case 5: { r = v; g = p; b = q; } + } + + return kha.Color.fromFloats(r, g, b); + } + + function drawTiles(tile: ShadowMapTile, atlas: ShadowMapAtlas, atlasVisualSize: Float) { + var color = kha.Color.fromFloats(0.1, 0.1, 0.1); + var borderColor = color; + var tileScale = (tile.size / atlas.sizew) * atlasVisualSize; //* 0.95; + var x = (tile.coordsX / atlas.sizew) * atlasVisualSize; + var y = (tile.coordsY / atlas.sizew) * atlasVisualSize; + + if (tile.light != null) { + color = lightColorMap.get(tile.light.name); + if (color == null) { + color = colorFromHSV(Math.random(), 0.7, Math.random() * 0.5 + 0.5); + + lightColorMap.set(tile.light.name, color); + lightColorMapCount++; + } + ui.fill(x + tileScale * 0.019, y + tileScale * 0.03, tileScale * 0.96, tileScale * 0.96, color); + } + ui.rect(x, y, tileScale, tileScale, borderColor); + + #if arm_shadowmap_atlas_lod + // draw children tiles + for (t in tile.tiles) + drawTiles(t, atlas, atlasVisualSize); + #end + } + ui.indent(false); ui.text("Constants:"); highLightNext(); @@ -540,46 +613,74 @@ class DebugConsole extends Trait { highLightNext(); ui.text('Tiles Used For Sun: ${ ShadowMapTile.tilesLightType("sun") }'); ui.unindent(false); + ui.indent(false); var i = 0; for (atlas in ShadowMapAtlas.shadowMapAtlases) { if (ui.panel(Id.handle({selected: false}).nest(i), atlas.target )) { ui.indent(false); + // Draw visual representation of the atlas + var atlasVisualSize = ui._windowW * 0.92; + + drawScale('${atlas.sizew}px', ui._y + ui.ELEMENT_H() * 0.9, ui._x, ui._x + atlasVisualSize); + + // reset light color map when lights are removed + if (lightColorMapCount > iron.Scene.active.lights.length) { + lightColorMap = new Map(); + lightColorMapCount = 0; + } + + for (tile in atlas.tiles) + drawTiles(tile, atlas, atlasVisualSize); + // set vertical space for atlas visual representation + ui._y += atlasVisualSize + 3; + + var tilesRow = atlas.currTileOffset == 0 ? 1 : atlas.currTileOffset; + var tileScale = atlasVisualSize / tilesRow; + drawScale('${atlas.baseTileSizeConst}px', ui._y, ui._x, ui._x + tileScale, true); + // general atlas information - ui.text('Current Size: ${atlas.sizew}, ${atlas.sizeh} px'); highLightNext(); - ui.text('Max Size: ${atlas.maxAtlasSizeConst}, ${atlas.maxAtlasSizeConst} px'); - ui.text('Tile Size: ${atlas.baseTileSizeConst}, ${atlas.baseTileSizeConst} px'); + ui.text('Max Atlas Size: ${atlas.maxAtlasSizeConst}, ${atlas.maxAtlasSizeConst} px'); highLightNext(); - #if arm_shadowmap_atlas_lod + // show detailed information per light - if (ui.panel(Id.handle({selected: false}).nest(i).nest(0), "Lights")) { + if (ui.panel(Id.handle({selected: false}).nest(i).nest(0), "Lights in Atlas")) { ui.indent(false); var j = 1; for (tile in atlas.activeTiles) { + var textCol = ui.t.TEXT_COL; + var lightCol = lightColorMap.get(tile.light.name); + if (lightCol != null) + ui.t.TEXT_COL = lightCol; + #if arm_shadowmap_atlas_lod if (ui.panel(Id.handle({selected: false}).nest(i).nest(j), tile.light.name)) { + ui.t.TEXT_COL = textCol; ui.indent(false); ui.text('Shadow Map Size: ${tile.size}, ${tile.size} px'); - ui.unindent(); + ui.unindent(false); } + #else + ui.indent(false); + ui.text(tile.light.name); + ui.unindent(false); + #end + ui.t.TEXT_COL = textCol; j++; } ui.unindent(false); } - #else - // show total lights occupied - ui.text('Current Active Lights: ${atlas.activeTiles.length}'); - #end + // show unused tiles statistics #if arm_shadowmap_atlas_lod // WIP #else var unusedTiles = atlas.tiles.length; #if arm_shadowmap_atlas_single_map - for (tile in atlas.activeTiles) - unusedTiles -= ShadowMapTile.tilesLightType(tile.light.data.raw.type); + for (tile in atlas.activeTiles) + unusedTiles -= ShadowMapTile.tilesLightType(tile.light.data.raw.type); #else - unusedTiles -= atlas.activeTiles.length * ShadowMapTile.tilesLightType(atlas.lightType); + unusedTiles -= atlas.activeTiles.length * ShadowMapTile.tilesLightType(atlas.lightType); #end ui.text('Unused tiles: ${unusedTiles}'); #end From eb01b4652a3e590987c52ddaf6b0bd70efce019a Mon Sep 17 00:00:00 2001 From: N8n5h Date: Sun, 18 Apr 2021 10:32:36 -0300 Subject: [PATCH 103/264] remove redundant 0 check when computing tile offset --- Sources/armory/renderpath/Inc.hx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/armory/renderpath/Inc.hx b/Sources/armory/renderpath/Inc.hx index 934da96f..0a536557 100644 --- a/Sources/armory/renderpath/Inc.hx +++ b/Sources/armory/renderpath/Inc.hx @@ -161,8 +161,8 @@ class Inc { // set the tile offset for this tile and every linked tile to this one var j = 0; tile.forEachTileLinked(function (lTile) { - tile.light.tileOffsetX[j] = (lTile.coordsX == 0) ? 0.0 : lTile.coordsX / atlas.sizew; - tile.light.tileOffsetY[j] = (lTile.coordsY == 0) ? 0.0 : lTile.coordsY / atlas.sizew; + tile.light.tileOffsetX[j] = lTile.coordsX / atlas.sizew; + tile.light.tileOffsetY[j] = lTile.coordsY / atlas.sizew; tile.light.tileScale[j] = lTile.size / atlas.sizew; j++; }); From 8a364259dbf07c2d9fb09ada776c9d5db1cc1ef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Mon, 19 Apr 2021 21:42:09 +0200 Subject: [PATCH 104/264] Fix creation of overlay materials --- blender/arm/material/make_overlay.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/blender/arm/material/make_overlay.py b/blender/arm/material/make_overlay.py index 4d13c412..47c71df3 100644 --- a/blender/arm/material/make_overlay.py +++ b/blender/arm/material/make_overlay.py @@ -1,8 +1,9 @@ -import arm.material.cycles as cycles -import arm.material.mat_state as mat_state +import arm.material.make_finalize as make_finalize import arm.material.make_mesh as make_mesh +import arm.material.mat_state as mat_state import arm.material.mat_utils as mat_utils + def make(context_id): con = { 'name': context_id, 'depth_write': True, 'compare_mode': 'less', 'cull_mode': 'clockwise' } mat = mat_state.material @@ -14,14 +15,14 @@ def make(context_id): con['alpha_blend_source'] = mat.arm_blending_source_alpha con['alpha_blend_destination'] = mat.arm_blending_destination_alpha con['alpha_blend_operation'] = mat.arm_blending_operation_alpha - + con_overlay = mat_state.data.add_context(con) arm_discard = mat.arm_discard is_transluc = mat_utils.is_transluc(mat) parse_opacity = (blend and is_transluc) or arm_discard make_mesh.make_base(con_overlay, parse_opacity=parse_opacity) - + frag = con_overlay.frag if arm_discard: @@ -36,4 +37,6 @@ def make(context_id): frag.write('fragColor.rgb = pow(fragColor.rgb, vec3(1.0 / 2.2));') + make_finalize.make(con_overlay) + return con_overlay From 97b578d0ed59f5b26fe8cbad56c63d1f37500bd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Mon, 19 Apr 2021 22:21:23 +0200 Subject: [PATCH 105/264] Fix export of attribute node if no UV map exists --- blender/arm/material/cycles_nodes/nodes_input.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/material/cycles_nodes/nodes_input.py b/blender/arm/material/cycles_nodes/nodes_input.py index 2a79dffd..69072bcd 100644 --- a/blender/arm/material/cycles_nodes/nodes_input.py +++ b/blender/arm/material/cycles_nodes/nodes_input.py @@ -41,7 +41,7 @@ def parse_attribute(node: bpy.types.ShaderNodeAttribute, out_socket: bpy.types.N lays = mat_user.data.uv_layers # First UV map referenced - if node.attribute_name == lays[0].name: + if len(lays) > 0 and node.attribute_name == lays[0].name: state.con.add_elem('tex', 'short2norm') return c.cast_value('vec3(texCoord.x, 1.0 - texCoord.y, 0.0)', from_type='vec3', to_type=out_type) From 9247b09c88556da71732853ab03df1283f4c6281 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Sat, 24 Apr 2021 20:31:03 -0300 Subject: [PATCH 106/264] Add profiling of shadow map atlas logic --- Sources/armory/renderpath/Inc.hx | 19 +++++++++++++++++++ Sources/armory/trait/internal/DebugConsole.hx | 16 ++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/Sources/armory/renderpath/Inc.hx b/Sources/armory/renderpath/Inc.hx index 0a536557..e2450a9d 100644 --- a/Sources/armory/renderpath/Inc.hx +++ b/Sources/armory/renderpath/Inc.hx @@ -113,6 +113,7 @@ class Inc { #end // add new lights to the atlases #if arm_debug + beginProfile(); // reset data on rejected lights for (atlas in ShadowMapAtlas.shadowMapAtlases) { atlas.rejectedLights = []; @@ -212,6 +213,9 @@ class Inc { tile.freeTile(); } } + #if arm_debug + endProfile(); + #end #end // rp_shadowmap } #else @@ -527,6 +531,21 @@ class Inc { return null; #end } + + #if arm_debug + public static var shadowMapAtlasTime = 0.0; + static var startTime = 0.0; + static var callBackSetup = false; + static function setupEndFrameCallback() { + if (!callBackSetup) { + callBackSetup = true; + iron.App.endFrameCallbacks.push(endFrame); + } + } + static function beginProfile() { setupEndFrameCallback(); startTime = kha.Scheduler.realTime(); } + static function endProfile() { shadowMapAtlasTime += kha.Scheduler.realTime() - startTime; } + public static function endFrame() { shadowMapAtlasTime = 0; } + #end } #if arm_shadowmap_atlas diff --git a/Sources/armory/trait/internal/DebugConsole.hx b/Sources/armory/trait/internal/DebugConsole.hx index 483de91f..4cc55a49 100755 --- a/Sources/armory/trait/internal/DebugConsole.hx +++ b/Sources/armory/trait/internal/DebugConsole.hx @@ -68,6 +68,8 @@ class DebugConsole extends Trait { #if arm_shadowmap_atlas var lightColorMap: Map = new Map(); var lightColorMapCount = 0; + var smaTime = 0.0; + var smaTimeAvg = 0.0; #end public function new(scaleFactor = 1.0, scaleDebugConsole = 1.0, positionDebugConsole = 2, visibleDebugConsole = 1, @@ -483,6 +485,12 @@ class DebugConsole extends Trait { ui.text("Physics"); ui.text(Math.round(physTimeAvg * 10000) / 10 + " ms", Align.Right); + #if arm_shadowmap_atlas + ui.row(lrow); + ui.text("Shadow Map Atlas (Script)"); + ui.text(Math.round(smaTimeAvg * 10000) / 10 + " ms", Align.Right); + #end + ui.unindent(); } @@ -868,6 +876,11 @@ class DebugConsole extends Trait { animTimeAvg = animTime / frames; physTimeAvg = physTime / frames; + #if arm_shadowmap_atlas + smaTimeAvg = smaTime / frames; + smaTime = 0; + #end + totalTime = 0; renderPathTime = 0; updateTime = 0; @@ -892,6 +905,9 @@ class DebugConsole extends Trait { #if arm_physics physTime += armory.trait.physics.PhysicsWorld.physTime; #end + #if arm_shadowmap_atlas + smaTime += armory.renderpath.Inc.shadowMapAtlasTime; + #end } static function roundfp(f: Float, precision = 2): Float { From 41c692423151b378bc4421873f4fa9e5b9a13588 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Sat, 24 Apr 2021 21:48:24 -0300 Subject: [PATCH 107/264] Separate shadow map atlas logic and rendering time --- Sources/armory/renderpath/Inc.hx | 24 +++++++++++++------ Sources/armory/trait/internal/DebugConsole.hx | 24 +++++++++++++------ 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/Sources/armory/renderpath/Inc.hx b/Sources/armory/renderpath/Inc.hx index e2450a9d..0ed68d0f 100644 --- a/Sources/armory/renderpath/Inc.hx +++ b/Sources/armory/renderpath/Inc.hx @@ -113,7 +113,7 @@ class Inc { #end // add new lights to the atlases #if arm_debug - beginProfile(); + beginShadowsLogicProfile(); // reset data on rejected lights for (atlas in ShadowMapAtlas.shadowMapAtlases) { atlas.rejectedLights = []; @@ -175,6 +175,9 @@ class Inc { var face = 0; var faces = ShadowMapTile.tilesLightType(tile.light.data.raw.type); + #if arm_debug + beginShadowsRenderProfile(); + #end tile.forEachTileLinked(function (lTile) { if (faces > 1) { #if arm_csm @@ -191,6 +194,9 @@ class Inc { path.drawMeshesStream("shadowmap"); }); + #if arm_debug + endShadowsRenderProfile(); + #end path.currentFace = -1; } @@ -214,7 +220,7 @@ class Inc { } } #if arm_debug - endProfile(); + endShadowsLogicProfile(); #end #end // rp_shadowmap } @@ -533,8 +539,10 @@ class Inc { } #if arm_debug - public static var shadowMapAtlasTime = 0.0; - static var startTime = 0.0; + public static var shadowsLogicTime = 0.0; + public static var shadowsRenderTime = 0.0; + static var startShadowsLogicTime = 0.0; + static var startShadowsRenderTime = 0.0; static var callBackSetup = false; static function setupEndFrameCallback() { if (!callBackSetup) { @@ -542,9 +550,11 @@ class Inc { iron.App.endFrameCallbacks.push(endFrame); } } - static function beginProfile() { setupEndFrameCallback(); startTime = kha.Scheduler.realTime(); } - static function endProfile() { shadowMapAtlasTime += kha.Scheduler.realTime() - startTime; } - public static function endFrame() { shadowMapAtlasTime = 0; } + static function beginShadowsLogicProfile() { setupEndFrameCallback(); startShadowsLogicTime = kha.Scheduler.realTime(); } + static function beginShadowsRenderProfile() { startShadowsRenderTime = kha.Scheduler.realTime(); } + static function endShadowsLogicProfile() { shadowsLogicTime += kha.Scheduler.realTime() - startShadowsLogicTime - shadowsRenderTime; } + static function endShadowsRenderProfile() { shadowsRenderTime += kha.Scheduler.realTime() - startShadowsRenderTime; } + public static function endFrame() { shadowsLogicTime = 0; shadowsRenderTime = 0; } #end } diff --git a/Sources/armory/trait/internal/DebugConsole.hx b/Sources/armory/trait/internal/DebugConsole.hx index 4cc55a49..25bfdc6e 100755 --- a/Sources/armory/trait/internal/DebugConsole.hx +++ b/Sources/armory/trait/internal/DebugConsole.hx @@ -68,8 +68,10 @@ class DebugConsole extends Trait { #if arm_shadowmap_atlas var lightColorMap: Map = new Map(); var lightColorMapCount = 0; - var smaTime = 0.0; - var smaTimeAvg = 0.0; + var smaLogicTime = 0.0; + var smaLogicTimeAvg = 0.0; + var smaRenderTime = 0.0; + var smaRenderTimeAvg = 0.0; #end public function new(scaleFactor = 1.0, scaleDebugConsole = 1.0, positionDebugConsole = 2, visibleDebugConsole = 1, @@ -487,8 +489,12 @@ class DebugConsole extends Trait { #if arm_shadowmap_atlas ui.row(lrow); - ui.text("Shadow Map Atlas (Script)"); - ui.text(Math.round(smaTimeAvg * 10000) / 10 + " ms", Align.Right); + ui.text("Shadow Map Atlas (Logic)"); + ui.text(Math.round(smaLogicTimeAvg * 10000) / 10 + " ms", Align.Right); + + ui.row(lrow); + ui.text("Shadow Map Atlas (Render)"); + ui.text(Math.round(smaRenderTimeAvg * 10000) / 10 + " ms", Align.Right); #end ui.unindent(); @@ -877,8 +883,11 @@ class DebugConsole extends Trait { physTimeAvg = physTime / frames; #if arm_shadowmap_atlas - smaTimeAvg = smaTime / frames; - smaTime = 0; + smaLogicTimeAvg = smaLogicTime / frames; + smaLogicTime = 0; + + smaRenderTimeAvg = smaRenderTime / frames; + smaRenderTime = 0; #end totalTime = 0; @@ -906,7 +915,8 @@ class DebugConsole extends Trait { physTime += armory.trait.physics.PhysicsWorld.physTime; #end #if arm_shadowmap_atlas - smaTime += armory.renderpath.Inc.shadowMapAtlasTime; + smaLogicTime += armory.renderpath.Inc.shadowsLogicTime; + smaRenderTime += armory.renderpath.Inc.shadowsRenderTime; #end } From ddc3da77998ff73a18ddd15b2cf3e2d7e17f6484 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Mon, 26 Apr 2021 16:44:49 -0300 Subject: [PATCH 108/264] Fix artifact with point light soft shadow in sm atlas --- Shaders/std/shadows.glsl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Shaders/std/shadows.glsl b/Shaders/std/shadows.glsl index cafde49e..2fea30c1 100755 --- a/Shaders/std/shadows.glsl +++ b/Shaders/std/shadows.glsl @@ -96,6 +96,7 @@ float PCFCube(samplerCubeShadow shadowMapCube, const vec3 lp, vec3 ml, const flo #ifdef _ShadowMapAtlas // transform "out-of-bounds" coordinates to the correct face/coordinate system +// https://www.khronos.org/opengl/wiki/File:CubeMapAxes.png vec2 transformOffsetedUV(const int faceIndex, out int newFaceIndex, vec2 uv) { if (uv.x < 0.0) { if (faceIndex == 0) { // X+ @@ -211,7 +212,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif - result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); + result += texture(shadowMap, vec3(uvtiled, compare)); uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, 1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; From 939649f492618d9fd452f3e5f45f9ecaa52e01e3 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Wed, 28 Apr 2021 19:08:30 -0300 Subject: [PATCH 109/264] Make tileSizes and TileSizesFactor non-static This solves an oversight that basically made all atlases share the their tileSizesFactor, a variable used to quickly lookup tile tile sizes depending on the shadowMapScale. So if cascade size was higher than cube map size, then point light atlases would use incorrect values and break. --- Sources/armory/renderpath/Inc.hx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Sources/armory/renderpath/Inc.hx b/Sources/armory/renderpath/Inc.hx index 0ed68d0f..ad44d2d8 100644 --- a/Sources/armory/renderpath/Inc.hx +++ b/Sources/armory/renderpath/Inc.hx @@ -573,8 +573,8 @@ class ShadowMapAtlas { public var activeTiles: Array = []; public var depth = 1; #if arm_shadowmap_atlas_lod - static var tileSizes: Array = []; - static var tileSizeFactor: Array = []; + var tileSizes: Array = []; + var tileSizeFactor: Array = []; #end public var updateRenderTarget = false; public static var shadowMapAtlases:Map = new Map(); // map a shadowmap atlas to their light type @@ -593,8 +593,7 @@ class ShadowMapAtlas { this.maxAtlasSizeConst = getMaxAtlasSize(light.data.raw.type); #if arm_shadowmap_atlas_lod - if (tileSizes.length == 0) - computeTileSizes(maxTileSize, depth); + computeTileSizes(maxTileSize, depth); #end #if arm_debug @@ -660,17 +659,17 @@ class ShadowMapAtlas { } #if arm_shadowmap_atlas_lod - static function computeTileSizes(maxTileSize: Int, depth: Int): Void { + function computeTileSizes(maxTileSize: Int, depth: Int): Void { // find the highest value based on the calculation done in the cluster code var base = LightObject.zToShadowMapScale(0, 16); var subdiv = base / depth; for(i in 0...depth){ - tileSizes.push(Std.int(maxTileSize / Math.pow(2, i))); - tileSizeFactor.push(base); + this.tileSizes.push(Std.int(maxTileSize / Math.pow(2, i))); + this.tileSizeFactor.push(base); base -= subdiv; } - tileSizes.push(0); - tileSizeFactor.push(0.0); + this.tileSizes.push(0); + this.tileSizeFactor.push(0.0); } #end From 22a557162fd907ef57d34e09636b2f6a408950b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 1 May 2021 00:04:25 +0200 Subject: [PATCH 110/264] Update Blender version information to 2.93 LTS --- blender/arm/props_ui.py | 6 +++--- blender/arm/utils.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index 1fcd6de0..9f32d353 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -1024,7 +1024,7 @@ class ArmoryPlayButton(bpy.types.Operator): # 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.83 LTS.') + self.report({'INFO'}, 'For Armory to work correctly, you need Blender 2.93 LTS.') if not arm.utils.check_saved(self): return {"CANCELLED"} @@ -1063,7 +1063,7 @@ class ArmoryBuildProjectButton(bpy.types.Operator): 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.83 LTS.') + self.report({'INFO'}, 'For Armory to work correctly, you need Blender 2.93 LTS.') if not arm.utils.check_saved(self): return {"CANCELLED"} @@ -1103,7 +1103,7 @@ class ArmoryPublishProjectButton(bpy.types.Operator): 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.83 LTS.') + self.report({'INFO'}, 'For Armory to work correctly, you need Blender 2.93 LTS.') if not arm.utils.check_saved(self): return {"CANCELLED"} diff --git a/blender/arm/utils.py b/blender/arm/utils.py index 88db6888..7b3af738 100755 --- a/blender/arm/utils.py +++ b/blender/arm/utils.py @@ -946,7 +946,7 @@ def get_link_web_server(): return '' if not hasattr(addon_prefs, 'link_web_server') else addon_prefs.link_web_server def compare_version_blender_arm(): - return not (bpy.app.version[0] != 2 or bpy.app.version[1] != 83) + return not (bpy.app.version[0] != 2 or bpy.app.version[1] != 93) def type_name_to_type(name: str) -> bpy.types.bpy_struct: """Return the Blender type given by its name, if registered.""" From 0085f6fbd77b8ca8223e7f4e6b23ecc9bb50fdf1 Mon Sep 17 00:00:00 2001 From: Lubos Lenco Date: Sat, 1 May 2021 11:30:23 +0200 Subject: [PATCH 111/264] Bump version --- blender/arm/props.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/props.py b/blender/arm/props.py index 5143c8c6..e4b950f7 100755 --- a/blender/arm/props.py +++ b/blender/arm/props.py @@ -11,7 +11,7 @@ import arm.proxy import arm.utils # Armory version -arm_version = '2021.4' +arm_version = '2021.5' arm_commit = '$Id$' def get_project_html5_copy(self): From c185882db804055ae4b4b73e51b938bc06f6bd65 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Sat, 1 May 2021 22:19:29 +0200 Subject: [PATCH 112/264] Revert commit that creates an object if not in scene. Returns object only if present in active scene --- Sources/armory/logicnode/GetObjectNode.hx | 33 ++----------------- .../logicnode/object/LN_get_object_by_name.py | 2 +- 2 files changed, 3 insertions(+), 32 deletions(-) diff --git a/Sources/armory/logicnode/GetObjectNode.hx b/Sources/armory/logicnode/GetObjectNode.hx index d0bdb60e..08c30c4c 100644 --- a/Sources/armory/logicnode/GetObjectNode.hx +++ b/Sources/armory/logicnode/GetObjectNode.hx @@ -14,36 +14,7 @@ class GetObjectNode extends LogicNode { override function get(from: Int): Dynamic { var objectName: String = inputs[0].get(); - - if (property0 == null || property0 == iron.Scene.active.raw.name) { - return iron.Scene.active.getChild(objectName); - } - - #if arm_json - property0 += ".json"; - #elseif arm_compress - property0 += ".lz4"; - #end - - var outObj: Null = null; - - // Create the object in the active scene if it is from an inactive scene - iron.data.Data.getSceneRaw(property0, (rawScene: TSceneFormat) -> { - var objData: Null = null; - - for (o in rawScene.objects) { - if (o.name == objectName) { - objData = o; - break; - } - } - if (objData == null) return; - - iron.Scene.active.createObject(objData, rawScene, null, null, (newObj: Object) -> { - outObj = newObj; - }); - }); - - return outObj; + + return iron.Scene.active.getChild(objectName); } } diff --git a/blender/arm/logicnode/object/LN_get_object_by_name.py b/blender/arm/logicnode/object/LN_get_object_by_name.py index fd8c893c..4330371e 100644 --- a/blender/arm/logicnode/object/LN_get_object_by_name.py +++ b/blender/arm/logicnode/object/LN_get_object_by_name.py @@ -4,7 +4,7 @@ from arm.logicnode.arm_nodes import * class GetObjectNode(ArmLogicTreeNode): - """Searches for a object that uses the given name and returns it.""" + """Searches for a object that uses the given name in the current active scene and returns it.""" bl_idname = 'LNGetObjectNode' bl_label = 'Get Object by Name' arm_version = 1 From 1ebe5284174e2d3d31acd3b37ce436cf70a28be2 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Sun, 2 May 2021 01:30:34 +0200 Subject: [PATCH 113/264] Remove scene port from logic node --- blender/arm/logicnode/object/LN_get_object_by_name.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/blender/arm/logicnode/object/LN_get_object_by_name.py b/blender/arm/logicnode/object/LN_get_object_by_name.py index 4330371e..ae39b4e1 100644 --- a/blender/arm/logicnode/object/LN_get_object_by_name.py +++ b/blender/arm/logicnode/object/LN_get_object_by_name.py @@ -5,19 +5,13 @@ from arm.logicnode.arm_nodes import * class GetObjectNode(ArmLogicTreeNode): """Searches for a object that uses the given name in the current active scene and returns it.""" + bl_idname = 'LNGetObjectNode' bl_label = 'Get Object by Name' arm_version = 1 - property0: PointerProperty( - type=bpy.types.Scene, name='Scene', - description='The scene from which to take the object') - def init(self, context): super(GetObjectNode, self).init(context) self.add_input('NodeSocketString', 'Name') self.add_output('ArmNodeSocketObject', 'Object') - - def draw_buttons(self, context, layout): - layout.prop_search(self, 'property0', bpy.data, "scenes") From badc8853b42ba7e5cc09690fef58fe70a8da0925 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Sun, 2 May 2021 01:31:44 +0200 Subject: [PATCH 114/264] Make description more accurate --- blender/arm/logicnode/object/LN_spawn_object.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/blender/arm/logicnode/object/LN_spawn_object.py b/blender/arm/logicnode/object/LN_spawn_object.py index 1288fc3f..e7bfe64a 100644 --- a/blender/arm/logicnode/object/LN_spawn_object.py +++ b/blender/arm/logicnode/object/LN_spawn_object.py @@ -1,7 +1,8 @@ from arm.logicnode.arm_nodes import * class SpawnObjectNode(ArmLogicTreeNode): - """Spawns the given object. The spawned object has the same name of its instance, but they are threated as different objects.""" + """Spawns the given object if present in the current active scene. The spawned object has the same name of its instance, but they are threated as different objects.""" + bl_idname = 'LNSpawnObjectNode' bl_label = 'Spawn Object' arm_version = 1 From 5b6875947c9bdf1e58678afaa064e6cdad98ce1f Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Sun, 2 May 2021 01:33:38 +0200 Subject: [PATCH 115/264] New logic node to spawn objects not in current active scene --- .../armory/logicnode/SpawnObjectByNameNode.hx | 72 +++++++++++++++++++ .../object/LN_spawn_object_by_name.py | 24 +++++++ 2 files changed, 96 insertions(+) create mode 100644 Sources/armory/logicnode/SpawnObjectByNameNode.hx create mode 100644 blender/arm/logicnode/object/LN_spawn_object_by_name.py diff --git a/Sources/armory/logicnode/SpawnObjectByNameNode.hx b/Sources/armory/logicnode/SpawnObjectByNameNode.hx new file mode 100644 index 00000000..5ecb1bc7 --- /dev/null +++ b/Sources/armory/logicnode/SpawnObjectByNameNode.hx @@ -0,0 +1,72 @@ +package armory.logicnode; + +import iron.data.SceneFormat.TSceneFormat; +import iron.data.Data; +import iron.object.Object; +import iron.math.Mat4; +import armory.trait.physics.RigidBody; + +class SpawnObjectByNameNode extends LogicNode { + + var object: Object; + var matrices: Array = []; + + /** Scene from which to take the object **/ + public var property0: Null; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var objectName = inputs[1].get(); + if (objectName == null) return; + + #if arm_json + property0 += ".json"; + #elseif arm_compress + property0 += ".lz4"; + #end + + var m: Mat4 = inputs[2].get(); + matrices.push(m != null ? m.clone() : null); + var spawnChildren: Bool = inputs.length > 3 ? inputs[3].get() : true; // TODO + + Data.getSceneRaw(property0, (rawScene: TSceneFormat) -> { + + //Check if object with given name present in the specified scene + var objPresent: Bool = false; + + for (o in rawScene.objects) { + if (o.name == objectName) { + objPresent = true; + break; + } + } + if (! objPresent) return; + + //Spawn object if present + iron.Scene.active.spawnObject(objectName, null, function(o: Object) { + object = o; + var matrix = matrices.pop(); // Async spawn in a loop, order is non-stable + if (matrix != null) { + object.transform.setMatrix(matrix); + #if arm_physics + var rigidBody = object.getTrait(RigidBody); + if (rigidBody != null) { + object.transform.buildMatrix(); + rigidBody.syncTransform(); + } + #end + } + object.visible = true; + runOutput(0); + }, spawnChildren, rawScene); + + }); + } + + override function get(from: Int): Dynamic { + return object; + } +} diff --git a/blender/arm/logicnode/object/LN_spawn_object_by_name.py b/blender/arm/logicnode/object/LN_spawn_object_by_name.py new file mode 100644 index 00000000..92faa105 --- /dev/null +++ b/blender/arm/logicnode/object/LN_spawn_object_by_name.py @@ -0,0 +1,24 @@ +from arm.logicnode.arm_nodes import * + +class SpawnObjectByNameNode(ArmLogicTreeNode): + """Spawns an object bearing the given name, even if not present in the active scene""" + bl_idname = 'LNSpawnObjectByNameNode' + bl_label = 'Spawn Object By Name' + arm_version = 1 + + property0: PointerProperty( + type=bpy.types.Scene, name='Scene', + description='The scene from which to take the object') + + def init(self, context): + super(SpawnObjectByNameNode, self).init(context) + self.add_input('ArmNodeSocketAction', 'In') + self.add_input('NodeSocketString', 'Name') + self.add_input('NodeSocketShader', 'Transform') + self.add_input('NodeSocketBool', 'Children', default_value=True) + + self.add_output('ArmNodeSocketAction', 'Out') + self.add_output('ArmNodeSocketObject', 'Object') + + def draw_buttons(self, context, layout): + layout.prop_search(self, 'property0', bpy.data, "scenes") From df372e952349564ccfc9daa8b856a03263cb0d0c Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Sun, 2 May 2021 01:37:00 +0200 Subject: [PATCH 116/264] Spelling correction --- blender/arm/logicnode/object/LN_spawn_object.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/logicnode/object/LN_spawn_object.py b/blender/arm/logicnode/object/LN_spawn_object.py index e7bfe64a..e4e3722a 100644 --- a/blender/arm/logicnode/object/LN_spawn_object.py +++ b/blender/arm/logicnode/object/LN_spawn_object.py @@ -1,7 +1,7 @@ from arm.logicnode.arm_nodes import * class SpawnObjectNode(ArmLogicTreeNode): - """Spawns the given object if present in the current active scene. The spawned object has the same name of its instance, but they are threated as different objects.""" + """Spawns the given object if present in the current active scene. The spawned object has the same name of its instance, but they are treated as different objects.""" bl_idname = 'LNSpawnObjectNode' bl_label = 'Spawn Object' From 2c761b2ff6ccd04e98ca1c464aab5ba81f0fd629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 6 May 2021 21:29:59 +0200 Subject: [PATCH 117/264] Slightly improve debug console panel UI --- blender/arm/props_ui.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index 9f32d353..00b13940 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -934,23 +934,20 @@ class ARM_PT_ProjectFlagsDebugConsolePanel(bpy.types.Panel): 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'] - row = layout.row() - row.enabled = wrd.arm_ui != 'Disabled' - row.prop(wrd, 'arm_debug_console') - row = layout.row() - row.enabled = wrd.arm_debug_console - row.prop(wrd, 'arm_debug_console_position') - row = layout.row() - row.enabled = wrd.arm_debug_console - row.prop(wrd, 'arm_debug_console_scale') - row = layout.row() - row.enabled = wrd.arm_debug_console - row.prop(wrd, 'arm_debug_console_visible') + 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" From 2f1fe4ef0bdc4bf6bce36418345f0a6f5951ea56 Mon Sep 17 00:00:00 2001 From: Lubos Lenco Date: Fri, 7 May 2021 15:24:03 +0200 Subject: [PATCH 118/264] Move Canvas and game ui to armory.ui package --- Sources/armory/trait/internal/CanvasScript.hx | 8 +- Sources/armory/ui/Canvas.hx | 410 ++++++++++++++++++ Sources/armory/ui/Ext.hx | 331 ++++++++++++++ Sources/armory/ui/Popup.hx | 127 ++++++ 4 files changed, 872 insertions(+), 4 deletions(-) create mode 100644 Sources/armory/ui/Canvas.hx create mode 100644 Sources/armory/ui/Ext.hx create mode 100644 Sources/armory/ui/Popup.hx diff --git a/Sources/armory/trait/internal/CanvasScript.hx b/Sources/armory/trait/internal/CanvasScript.hx index ba8415e6..f8c6d82f 100644 --- a/Sources/armory/trait/internal/CanvasScript.hx +++ b/Sources/armory/trait/internal/CanvasScript.hx @@ -3,7 +3,7 @@ package armory.trait.internal; import iron.Trait; #if arm_ui import zui.Zui; -import zui.Canvas; +import armory.ui.Canvas; #end class CanvasScript extends Trait { @@ -62,7 +62,7 @@ class CanvasScript extends Trait { notifyOnRender2D(function(g: kha.graphics2.Graphics) { if (canvas == null) return; - + setCanvasDimensions(kha.System.windowWidth(), kha.System.windowHeight()); var events = Canvas.draw(cui, canvas, g); @@ -121,7 +121,7 @@ class CanvasScript extends Trait { public function setCanvasVisibility(visible: Bool){ for (e in canvas.elements) e.visible = visible; } - + /** * Set dimensions of canvas * @param x Width @@ -140,7 +140,7 @@ class CanvasScript extends Trait { } // Contains data - @:access(zui.Canvas) + @:access(armory.ui.Canvas) @:access(zui.Handle) public function getHandle(name: String): Handle { // Consider this a temporary solution diff --git a/Sources/armory/ui/Canvas.hx b/Sources/armory/ui/Canvas.hx new file mode 100644 index 00000000..2c66cf68 --- /dev/null +++ b/Sources/armory/ui/Canvas.hx @@ -0,0 +1,410 @@ +package armory.ui; + +import zui.Zui; + +using kha.graphics2.GraphicsExtension; + +@:access(zui.Zui) +class Canvas { + + public static var assetMap = new Map(); // kha.Image | kha.Font + public static var themes = new Array(); + static var events:Array = []; + + public static var screenW = -1; + public static var screenH = -1; + public static var locale = "en"; + static var _ui: Zui; + static var h = new zui.Zui.Handle(); // TODO: needs one handle per canvas + + public static function draw(ui: Zui, canvas: TCanvas, g: kha.graphics2.Graphics): Array { + + screenW = kha.System.windowWidth(); + screenH = kha.System.windowHeight(); + + events.resize(0); + + _ui = ui; + + g.end(); + ui.begin(g); // Bake elements + g.begin(false); + + ui.g = g; + + for (elem in canvas.elements) { + if (elem.parent == null) drawElement(ui, canvas, elem); + } + + g.end(); + ui.end(); // Finish drawing + g.begin(false); + + return events; + } + + static function drawElement(ui: Zui, canvas: TCanvas, element: TElement, px = 0.0, py = 0.0) { + + if (element == null || element.visible == false) return; + + var anchorOffset = getAnchorOffset(canvas, element); + px += anchorOffset[0]; + py += anchorOffset[1]; + + ui._x = canvas.x + scaled(element.x) + px; + ui._y = canvas.y + scaled(element.y) + py; + ui._w = scaled(element.width); + + var rotated = element.rotation != null && element.rotation != 0; + if (rotated) ui.g.pushRotation(element.rotation, ui._x + scaled(element.width) / 2, ui._y + scaled(element.height) / 2); + + switch (element.type) { + case Text: + var font = ui.ops.font; + var size = ui.fontSize; + + var fontAsset = element.asset != null && StringTools.endsWith(element.asset, ".ttf"); + if (fontAsset) ui.ops.font = getAsset(canvas, element.asset); + ui.fontSize = scaled(element.height); + ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.text(getText(canvas, element), element.alignment); + + ui.ops.font = font; + ui.fontSize = size; + + case Button: + var eh = ui.t.ELEMENT_H; + var bh = ui.t.BUTTON_H; + ui.t.ELEMENT_H = element.height; + ui.t.BUTTON_H = element.height; + ui.t.BUTTON_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.BUTTON_TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).BUTTON_TEXT_COL); + ui.t.BUTTON_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL); + ui.t.BUTTON_PRESSED_COL = getColor(element.color_press, getTheme(canvas.theme).BUTTON_PRESSED_COL); + if (ui.button(getText(canvas, element), element.alignment)) { + var e = element.event; + if (e != null && e != "") events.push(e); + } + ui.t.ELEMENT_H = eh; + ui.t.BUTTON_H = bh; + + case Image: + var image = getAsset(canvas, element.asset); + var fontAsset = element.asset != null && StringTools.endsWith(element.asset, ".ttf"); + if (image != null && !fontAsset) { + ui.imageScrollAlign = false; + var tint = element.color != null ? element.color : 0xffffffff; + if (ui.image(image, tint, scaled(element.height)) == zui.Zui.State.Released) { + var e = element.event; + if (e != null && e != "") events.push(e); + } + ui.imageScrollAlign = true; + } + + case FRectangle: + var col = ui.g.color; + ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.g.fillRect(ui._x, ui._y, ui._w, scaled(element.height)); + ui.g.color = col; + + case FCircle: + var col = ui.g.color; + ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.g.fillCircle(ui._x + (scaled(element.width) / 2), ui._y + (scaled(element.height) / 2), ui._w / 2); + ui.g.color = col; + + case Rectangle: + var col = ui.g.color; + ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.g.drawRect(ui._x, ui._y, ui._w, scaled(element.height), element.strength); + ui.g.color = col; + + case Circle: + var col = ui.g.color; + ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.g.drawCircle(ui._x+(scaled(element.width) / 2), ui._y + (scaled(element.height) / 2), ui._w / 2, element.strength); + ui.g.color = col; + + case FTriangle: + var col = ui.g.color; + ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.g.fillTriangle(ui._x + (ui._w / 2), ui._y, ui._x, ui._y + scaled(element.height), ui._x + ui._w, ui._y + scaled(element.height)); + ui.g.color = col; + + case Triangle: + var col = ui.g.color; + ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.g.drawLine(ui._x + (ui._w / 2), ui._y, ui._x, ui._y + scaled(element.height), element.strength); + ui.g.drawLine(ui._x, ui._y + scaled(element.height), ui._x + ui._w, ui._y + scaled(element.height), element.strength); + ui.g.drawLine(ui._x + ui._w, ui._y + scaled(element.height), ui._x + (ui._w / 2), ui._y, element.strength); + ui.g.color = col; + + case Check: + ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL); + ui.check(h.nest(element.id), getText(canvas, element)); + + case Radio: + ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL); + zui.Ext.inlineRadio(ui, h.nest(element.id), getText(canvas, element).split(";")); + + case Combo: + ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.LABEL_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.SEPARATOR_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL); + ui.combo(h.nest(element.id), getText(canvas, element).split(";")); + + case Slider: + ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.LABEL_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL); + ui.slider(h.nest(element.id), getText(canvas, element), 0.0, 1.0, true, 100, true, element.alignment); + + case TextInput: + ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.LABEL_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL); + ui.textInput(h.nest(element.id), getText(canvas, element), element.alignment); + if (h.nest(element.id).changed) { + var e = element.event; + if (e != null && e != "") events.push(e); + } + + case TextArea: + ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.LABEL_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL); + h.nest(element.id).text = getText(canvas, element); + zui.Ext.textArea(ui,h.nest(element.id), element.alignment,element.editable); + if (h.nest(element.id).changed) { + var e = element.event; + if (e != null && e != "") events.push(e); + } + + case KeyInput: + ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.LABEL_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL); + Ext.keyInput(ui, h.nest(element.id), getText(canvas, element)); + + case ProgressBar: + var col = ui.g.color; + var progress = element.progress_at; + var totalprogress = element.progress_total; + ui.g.color = getColor(element.color_progress, getTheme(canvas.theme).TEXT_COL); + ui.g.fillRect(ui._x, ui._y, ui._w / totalprogress * Math.min(progress, totalprogress), scaled(element.height)); + ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.g.drawRect(ui._x, ui._y, ui._w, scaled(element.height), element.strength); + ui.g.color = col; + + case CProgressBar: + var col = ui.g.color; + var progress = element.progress_at; + var totalprogress = element.progress_total; + ui.g.color = getColor(element.color_progress, getTheme(canvas.theme).TEXT_COL); + ui.g.drawArc(ui._x + (scaled(element.width) / 2), ui._y + (scaled(element.height) / 2), ui._w / 2, -Math.PI / 2, ((Math.PI * 2) / totalprogress * progress) - Math.PI / 2, element.strength); + ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.g.fillCircle(ui._x + (scaled(element.width) / 2), ui._y + (scaled(element.height) / 2), (ui._w / 2) - 10); + ui.g.color = col; + case Empty: + } + + if (element.children != null) { + for (id in element.children) { + drawElement(ui, canvas, elemById(canvas, id), scaled(element.x) + px, scaled(element.y) + py); + } + } + + if (rotated) ui.g.popTransformation(); + } + + static inline function getText(canvas: TCanvas, e: TElement): String { + return e.text; + } + + public static function getAsset(canvas: TCanvas, asset: String): Dynamic { // kha.Image | kha.Font { + for (a in canvas.assets) if (a.name == asset) return assetMap.get(a.id); + return null; + } + + static var elemId = -1; + public static function getElementId(canvas: TCanvas): Int { + if (elemId == -1) for (e in canvas.elements) if (elemId < e.id) elemId = e.id; + return ++elemId; + } + + static var assetId = -1; + public static function getAssetId(canvas: TCanvas): Int { + if (assetId == -1) for (a in canvas.assets) if (assetId < a.id) assetId = a.id; + return ++assetId; + } + + static function elemById(canvas: TCanvas, id: Int): TElement { + for (e in canvas.elements) if (e.id == id) return e; + return null; + } + + static inline function scaled(f: Float): Int { + return Std.int(f * _ui.SCALE()); + } + + public static inline function getColor(color: Null, defaultColor: Int): Int { + return color != null ? color : defaultColor; + } + + public static function getTheme(theme: String): zui.Themes.TTheme { + for (t in Canvas.themes) { + if (t.NAME == theme) return t; + } + return null; + } + + /** + Returns the positional scaled offset of the given element based on its anchor setting. + @param canvas The canvas object + @param element The element + @return Array [xOffset, yOffset] + **/ + public static function getAnchorOffset(canvas: TCanvas, element: TElement): Array { + var boxWidth, boxHeight: Float; + var offsetX = 0.0; + var offsetY = 0.0; + + if (element.parent == null) { + boxWidth = canvas.width; + boxHeight = canvas.height; + } + else { + var parent = elemById(canvas, element.parent); + boxWidth = scaled(parent.width); + boxHeight = scaled(parent.height); + } + + switch (element.anchor) { + case Top: + offsetX += boxWidth / 2 - scaled(element.width) / 2; + case TopRight: + offsetX += boxWidth - scaled(element.width); + case CenterLeft: + offsetY += boxHeight / 2 - scaled(element.height) / 2; + case Center: + offsetX += boxWidth / 2 - scaled(element.width) / 2; + offsetY += boxHeight / 2 - scaled(element.height) / 2; + case CenterRight: + offsetX += boxWidth - scaled(element.width); + offsetY += boxHeight / 2 - scaled(element.height) / 2; + case BottomLeft: + offsetY += boxHeight - scaled(element.height); + case Bottom: + offsetX += boxWidth / 2 - scaled(element.width) / 2; + offsetY += boxHeight - scaled(element.height); + case BottomRight: + offsetX += boxWidth - scaled(element.width); + offsetY += boxHeight - scaled(element.height); + } + + return [offsetX, offsetY]; + } +} + +typedef TCanvas = { + var name: String; + var x: Float; + var y: Float; + var width: Int; + var height: Int; + var elements: Array; + var theme: String; + @:optional var assets: Array; + @:optional var locales: Array; +} + +typedef TElement = { + var id: Int; + var type: ElementType; + var name: String; + var x: Float; + var y: Float; + var width: Int; + var height: Int; + @:optional var rotation: Null; + @:optional var text: String; + @:optional var event: String; + // null = follow theme settings + @:optional var color: Null; + @:optional var color_text: Null; + @:optional var color_hover: Null; + @:optional var color_press: Null; + @:optional var color_progress: Null; + @:optional var progress_at: Null; + @:optional var progress_total: Null; + @:optional var strength: Null; + @:optional var alignment: Null; + @:optional var anchor: Null; + @:optional var parent: Null; // id + @:optional var children: Array; // ids + @:optional var asset: String; + @:optional var visible: Null; + @:optional var editable: Null; +} + +typedef TAsset = { + var id: Int; + var name: String; + var file: String; +} + +typedef TLocale = { + var name: String; // "en" + var texts: Array; +} + +typedef TTranslatedText = { + var id: Int; // element id + var text: String; +} + +@:enum abstract ElementType(Int) from Int to Int { + var Text = 0; + var Image = 1; + var Button = 2; + var Empty = 3; + // var HLayout = 4; + // var VLayout = 5; + var Check = 6; + var Radio = 7; + var Combo = 8; + var Slider = 9; + var TextInput = 10; + var KeyInput = 11; + var FRectangle = 12; + var Rectangle = 13; + var FCircle = 14; + var Circle = 15; + var FTriangle = 16; + var Triangle = 17; + var ProgressBar = 18; + var CProgressBar = 19; + var TextArea = 20; +} + +@:enum abstract Anchor(Int) from Int to Int { + var TopLeft = 0; + var Top = 1; + var TopRight = 2; + var CenterLeft = 3; + var Center = 4; + var CenterRight = 5; + var BottomLeft = 6; + var Bottom = 7; + var BottomRight = 8; +} diff --git a/Sources/armory/ui/Ext.hx b/Sources/armory/ui/Ext.hx new file mode 100644 index 00000000..0104ddd5 --- /dev/null +++ b/Sources/armory/ui/Ext.hx @@ -0,0 +1,331 @@ +package armory.ui; + +import zui.Zui; +import kha.input.Keyboard; +import kha.input.KeyCode; + +typedef ListOpts = { + ?addCb: String->Void, + ?removeCb: Int->Void, + ?getNameCb: Int->String, + ?setNameCb: Int->String->Void, + ?getLabelCb: Int->String, + ?itemDrawCb: Handle->Int->Void, + ?showRadio: Bool, // false + ?editable: Bool, // true + ?showAdd: Bool, // true + ?addLabel: String // 'Add' +} + +@:access(zui.Zui) +class Ext { + public static function keyInput(ui: Zui, handle: Handle, label = "", align: Align = Left): Int { + if (!ui.isVisible(ui.ELEMENT_H())) { + ui.endElement(); + return Std.int(handle.value); + } + + var hover = ui.getHover(); + if (hover && Zui.onTextHover != null) Zui.onTextHover(); + ui.g.color = hover ? ui.t.ACCENT_HOVER_COL : ui.t.ACCENT_COL; // Text bg + ui.drawRect(ui.g, ui.t.FILL_ACCENT_BG, ui._x + ui.buttonOffsetY, ui._y + ui.buttonOffsetY, ui._w - ui.buttonOffsetY * 2, ui.BUTTON_H()); + + var startEdit = ui.getReleased() || ui.tabPressed; + if (ui.textSelectedHandle != handle && startEdit) ui.startTextEdit(handle); + if (ui.textSelectedHandle == handle) Ext.listenToKey(ui, handle); + else handle.changed = false; + + if (label != "") { + ui.g.color = ui.t.LABEL_COL; // Label + var labelAlign = align == Align.Right ? Align.Left : Align.Right; + var xOffset = labelAlign == Align.Left ? 7 : 0; + ui.drawString(ui.g, label, xOffset, 0, labelAlign); + } + + handle.text = Ext.keycodeToString(Std.int(handle.value)); + + ui.g.color = ui.t.TEXT_COL; // Text + ui.textSelectedHandle != handle ? ui.drawString(ui.g, handle.text, null, 0, align) : ui.drawString(ui.g, ui.textSelected, null, 0, align); + + ui.endElement(); + + return Std.int(handle.value); + } + + static function listenToKey(ui: Zui, handle: Handle) { + if (ui.isKeyDown) { + handle.value = ui.key; + handle.changed = ui.changed = true; + + ui.textSelectedHandle = null; + ui.isTyping = false; + + if (Keyboard.get() != null) Keyboard.get().hide(); + } + else { + ui.textSelected = "Press a key..."; + } + } + + public static function list(ui: Zui, handle: Handle, ar: Array, ?opts: ListOpts ): Int { + var selected = 0; + if (opts == null) opts = {}; + + var addCb = opts.addCb != null ? opts.addCb : function(name: String) ar.push(name); + var removeCb = opts.removeCb != null ? opts.removeCb : function(i: Int) ar.splice(i, 1); + var getNameCb = opts.getNameCb != null ? opts.getNameCb : function(i: Int) return ar[i]; + var setNameCb = opts.setNameCb != null ? opts.setNameCb : function(i: Int, name: String) ar[i] = name; + var getLabelCb = opts.getLabelCb != null ? opts.getLabelCb : function(i: Int) return ""; + var itemDrawCb = opts.itemDrawCb; + var showRadio = opts.showRadio != null ? opts.showRadio : false; + var editable = opts.editable != null ? opts.editable : true; + var showAdd = opts.showAdd != null ? opts.showAdd : true; + var addLabel = opts.addLabel != null ? opts.addLabel : "Add"; + + var i = 0; + while (i < ar.length) { + if (showRadio) { // Prepend ratio button + ui.row([0.12, 0.68, 0.2]); + if (ui.radio(handle.nest(0), i, "")) { + selected = i; + } + } + else ui.row([0.8, 0.2]); + + var itemHandle = handle.nest(i); + itemHandle.text = getNameCb(i); + editable ? setNameCb(i, ui.textInput(itemHandle, getLabelCb(i))) : ui.text(getNameCb(i)); + if (ui.button("X")) removeCb(i); + else i++; + + if (itemDrawCb != null) itemDrawCb(itemHandle.nest(i), i - 1); + } + if (showAdd && ui.button(addLabel)) addCb("untitled"); + + return selected; + } + + public static function panelList(ui: Zui, handle: Handle, ar: Array, + addCb: String->Void = null, + removeCb: Int->Void = null, + getNameCb: Int->String = null, + setNameCb: Int->String->Void = null, + itemDrawCb: Handle->Int->Void = null, + editable = true, + showAdd = true, + addLabel: String = "Add") { + + if (addCb == null) addCb = function(name: String) { ar.push(name); }; + if (removeCb == null) removeCb = function(i: Int) { ar.splice(i, 1); }; + if (getNameCb == null) getNameCb = function(i: Int) { return ar[i]; }; + if (setNameCb == null) setNameCb = function(i: Int, name: String) { ar[i] = name; }; + + var i = 0; + while (i < ar.length) { + ui.row([0.12, 0.68, 0.2]); + var expanded = ui.panel(handle.nest(i), ""); + + var itemHandle = handle.nest(i); + editable ? setNameCb(i, ui.textInput(itemHandle, getNameCb(i))) : ui.text(getNameCb(i)); + if (ui.button("X")) removeCb(i); + else i++; + + if (itemDrawCb != null && expanded) itemDrawCb(itemHandle.nest(i), i - 1); + } + if (showAdd && ui.button(addLabel)) { + addCb("untitled"); + } + } + + public static function colorField(ui: Zui, handle:Handle, alpha = false): Int { + ui.g.color = handle.color; + + ui.drawRect(ui.g, true, ui._x + 2, ui._y + ui.buttonOffsetY, ui._w - 4, ui.BUTTON_H()); + ui.g.color = ui.getHover() ? ui.t.ACCENT_HOVER_COL : ui.t.ACCENT_COL; + ui.drawRect(ui.g, false, ui._x + 2, ui._y + ui.buttonOffsetY, ui._w - 4, ui.BUTTON_H(), 1.0); + + if (ui.getStarted()) { + Popup.showCustom( + new Zui(ui.ops), + function(ui:Zui) { + zui.Ext.colorWheel(ui, handle, alpha); + }, + Std.int(ui.inputX), Std.int(ui.inputY), 200, 500); + } + + ui.endElement(); + return handle.color; + } + + public static function colorPicker(ui: Zui, handle: Handle, alpha = false): Int { + var r = ui.slider(handle.nest(0, {value: handle.color.R}), "R", 0, 1, true); + var g = ui.slider(handle.nest(1, {value: handle.color.G}), "G", 0, 1, true); + var b = ui.slider(handle.nest(2, {value: handle.color.B}), "B", 0, 1, true); + var a = handle.color.A; + if (alpha) a = ui.slider(handle.nest(3, {value: a}), "A", 0, 1, true); + var col = kha.Color.fromFloats(r, g, b, a); + ui.text("", Right, col); + return col; + } + + /** + Keycodes can be found here: http://api.kha.tech/kha/input/KeyCode.html + **/ + static function keycodeToString(keycode: Int): String { + switch (keycode) { + case -1: return "None"; + case KeyCode.Unknown: return "Unknown"; + case KeyCode.Back: return "Back"; + case KeyCode.Cancel: return "Cancel"; + case KeyCode.Help: return "Help"; + case KeyCode.Backspace: return "Backspace"; + case KeyCode.Tab: return "Tab"; + case KeyCode.Clear: return "Clear"; + case KeyCode.Return: return "Return"; + case KeyCode.Shift: return "Shift"; + case KeyCode.Control: return "Ctrl"; + case KeyCode.Alt: return "Alt"; + case KeyCode.Pause: return "Pause"; + case KeyCode.CapsLock: return "CapsLock"; + case KeyCode.Kana: return "Kana"; + // case KeyCode.Hangul: return "Hangul"; // Hangul == Kana + case KeyCode.Eisu: return "Eisu"; + case KeyCode.Junja: return "Junja"; + case KeyCode.Final: return "Final"; + case KeyCode.Hanja: return "Hanja"; + // case KeyCode.Kanji: return "Kanji"; // Kanji == Hanja + case KeyCode.Escape: return "Esc"; + case KeyCode.Convert: return "Convert"; + case KeyCode.NonConvert: return "NonConvert"; + case KeyCode.Accept: return "Accept"; + case KeyCode.ModeChange: return "ModeChange"; + case KeyCode.Space: return "Space"; + case KeyCode.PageUp: return "PageUp"; + case KeyCode.PageDown: return "PageDown"; + case KeyCode.End: return "End"; + case KeyCode.Home: return "Home"; + case KeyCode.Left: return "Left"; + case KeyCode.Up: return "Up"; + case KeyCode.Right: return "Right"; + case KeyCode.Down: return "Down"; + case KeyCode.Select: return "Select"; + case KeyCode.Print: return "Print"; + case KeyCode.Execute: return "Execute"; + case KeyCode.PrintScreen: return "PrintScreen"; + case KeyCode.Insert: return "Insert"; + case KeyCode.Delete: return "Delete"; + case KeyCode.Colon: return "Colon"; + case KeyCode.Semicolon: return "Semicolon"; + case KeyCode.LessThan: return "LessThan"; + case KeyCode.Equals: return "Equals"; + case KeyCode.GreaterThan: return "GreaterThan"; + case KeyCode.QuestionMark: return "QuestionMark"; + case KeyCode.At: return "At"; + case KeyCode.Win: return "Win"; + case KeyCode.ContextMenu: return "ContextMenu"; + case KeyCode.Sleep: return "Sleep"; + case KeyCode.Numpad0: return "Numpad0"; + case KeyCode.Numpad1: return "Numpad1"; + case KeyCode.Numpad2: return "Numpad2"; + case KeyCode.Numpad3: return "Numpad3"; + case KeyCode.Numpad4: return "Numpad4"; + case KeyCode.Numpad5: return "Numpad5"; + case KeyCode.Numpad6: return "Numpad6"; + case KeyCode.Numpad7: return "Numpad7"; + case KeyCode.Numpad8: return "Numpad8"; + case KeyCode.Numpad9: return "Numpad9"; + case KeyCode.Multiply: return "Multiply"; + case KeyCode.Add: return "Add"; + case KeyCode.Separator: return "Separator"; + case KeyCode.Subtract: return "Subtract"; + case KeyCode.Decimal: return "Decimal"; + case KeyCode.Divide: return "Divide"; + case KeyCode.F1: return "F1"; + case KeyCode.F2: return "F2"; + case KeyCode.F3: return "F3"; + case KeyCode.F4: return "F4"; + case KeyCode.F5: return "F5"; + case KeyCode.F6: return "F6"; + case KeyCode.F7: return "F7"; + case KeyCode.F8: return "F8"; + case KeyCode.F9: return "F9"; + case KeyCode.F10: return "F10"; + case KeyCode.F11: return "F11"; + case KeyCode.F12: return "F12"; + case KeyCode.F13: return "F13"; + case KeyCode.F14: return "F14"; + case KeyCode.F15: return "F15"; + case KeyCode.F16: return "F16"; + case KeyCode.F17: return "F17"; + case KeyCode.F18: return "F18"; + case KeyCode.F19: return "F19"; + case KeyCode.F20: return "F20"; + case KeyCode.F21: return "F21"; + case KeyCode.F22: return "F22"; + case KeyCode.F23: return "F23"; + case KeyCode.F24: return "F24"; + case KeyCode.NumLock: return "NumLock"; + case KeyCode.ScrollLock: return "ScrollLock"; + case KeyCode.WinOemFjJisho: return "WinOemFjJisho"; + case KeyCode.WinOemFjMasshou: return "WinOemFjMasshou"; + case KeyCode.WinOemFjTouroku: return "WinOemFjTouroku"; + case KeyCode.WinOemFjLoya: return "WinOemFjLoya"; + case KeyCode.WinOemFjRoya: return "WinOemFjRoya"; + case KeyCode.Circumflex: return "Circumflex"; + case KeyCode.Exclamation: return "Exclamation"; + case KeyCode.DoubleQuote: return "DoubleQuote"; + case KeyCode.Hash: return "Hash"; + case KeyCode.Dollar: return "Dollar"; + case KeyCode.Percent: return "Percent"; + case KeyCode.Ampersand: return "Ampersand"; + case KeyCode.Underscore: return "Underscore"; + case KeyCode.OpenParen: return "OpenParen"; + case KeyCode.CloseParen: return "CloseParen"; + case KeyCode.Asterisk: return "Asterisk"; + case KeyCode.Plus: return "Plus"; + case KeyCode.Pipe: return "Pipe"; + case KeyCode.HyphenMinus: return "HyphenMinus"; + case KeyCode.OpenCurlyBracket: return "OpenCurlyBracket"; + case KeyCode.CloseCurlyBracket: return "CloseCurlyBracket"; + case KeyCode.Tilde: return "Tilde"; + case KeyCode.VolumeMute: return "VolumeMute"; + case KeyCode.VolumeDown: return "VolumeDown"; + case KeyCode.VolumeUp: return "VolumeUp"; + case KeyCode.Comma: return "Comma"; + case KeyCode.Period: return "Period"; + case KeyCode.Slash: return "Slash"; + case KeyCode.BackQuote: return "BackQuote"; + case KeyCode.OpenBracket: return "OpenBracket"; + case KeyCode.BackSlash: return "BackSlash"; + case KeyCode.CloseBracket: return "CloseBracket"; + case KeyCode.Quote: return "Quote"; + case KeyCode.Meta: return "Meta"; + case KeyCode.AltGr: return "AltGr"; + case KeyCode.WinIcoHelp: return "WinIcoHelp"; + case KeyCode.WinIco00: return "WinIco00"; + case KeyCode.WinIcoClear: return "WinIcoClear"; + case KeyCode.WinOemReset: return "WinOemReset"; + case KeyCode.WinOemJump: return "WinOemJump"; + case KeyCode.WinOemPA1: return "WinOemPA1"; + case KeyCode.WinOemPA2: return "WinOemPA2"; + case KeyCode.WinOemPA3: return "WinOemPA3"; + case KeyCode.WinOemWSCTRL: return "WinOemWSCTRL"; + case KeyCode.WinOemCUSEL: return "WinOemCUSEL"; + case KeyCode.WinOemATTN: return "WinOemATTN"; + case KeyCode.WinOemFinish: return "WinOemFinish"; + case KeyCode.WinOemCopy: return "WinOemCopy"; + case KeyCode.WinOemAuto: return "WinOemAuto"; + case KeyCode.WinOemENLW: return "WinOemENLW"; + case KeyCode.WinOemBackTab: return "WinOemBackTab"; + case KeyCode.ATTN: return "ATTN"; + case KeyCode.CRSEL: return "CRSEL"; + case KeyCode.EXSEL: return "EXSEL"; + case KeyCode.EREOF: return "EREOF"; + case KeyCode.Play: return "Play"; + case KeyCode.Zoom: return "Zoom"; + case KeyCode.PA1: return "PA1"; + case KeyCode.WinOemClear: return "WinOemClear"; + } + return String.fromCharCode(keycode); + } +} diff --git a/Sources/armory/ui/Popup.hx b/Sources/armory/ui/Popup.hx new file mode 100644 index 00000000..1cfa4b49 --- /dev/null +++ b/Sources/armory/ui/Popup.hx @@ -0,0 +1,127 @@ +package armory.ui; + +import zui.Zui; +import kha.System; + +@:access(zui.Zui) +class Popup { + public static var show = false; + + static var ui: Zui = null; + static var hwnd = new Handle(); + static var boxTitle = ""; + static var boxText = ""; + static var boxCommands: Zui->Void = null; + static var modalX = 0; + static var modalY = 0; + static var modalW = 400; + static var modalH = 160; + + public static function render(g: kha.graphics2.Graphics) { + if (boxCommands == null) { + ui.begin(g); + if (ui.window(hwnd, modalX, modalY, modalW, modalH)) { + drawTitle(g); + + for (line in boxText.split("\n")) { + ui.text(line); + } + + ui._y = ui._h - ui.t.BUTTON_H - 10; + ui.row([1/3, 1/3, 1/3]); + ui.endElement(); + if (ui.button("OK")) { + show = false; + } + } + ui.end(); + } + else { + ui.begin(g); + if (ui.window(hwnd, modalX, modalY, modalW, modalH)) { + drawTitle(g); + + ui._y += 10; + boxCommands(ui); + } + ui.end(); + } + } + + public static function drawTitle(g: kha.graphics2.Graphics) { + if (boxTitle != "") { + g.color = ui.t.SEPARATOR_COL; + ui.drawRect(g, true, ui._x, ui._y, ui._w, ui.t.BUTTON_H); + + g.color = ui.t.TEXT_COL; + ui.text(boxTitle); + } + } + + public static function update() { + var inUse = ui.comboSelectedHandle != null; + + // Close popup + if (ui.inputStarted && !inUse) { + if (ui.inputX < modalX || ui.inputX > modalX + modalW || ui.inputY < modalY || ui.inputY > modalY + modalH) { + show = false; + } + } + } + + /** + Displays a message box with a title, a text body and a centered "OK" button. + @param ui the Zui instance for the popup + @param title the title to display + @param text the text to display + **/ + public static function showMessage(ui: Zui, title: String, text: String) { + Popup.ui = ui; + init(); + + boxTitle = title; + boxText = text; + boxCommands = null; + } + + /** + Displays a popup box with custom drawing code. + @param ui the Zui instance for the popup + @param commands the function for drawing the popup's content + @param mx the x position of the popup. -1 = screen center (defaults to -1) + @param my the y position of the popup. -1 = screen center (defaults to -1) + @param mw the width of the popup (defaults to 400) + @param mh the height of the popup (defaults to 160) + **/ + public static function showCustom(ui: Zui, commands: Zui->Void = null, mx = -1, my = -1, mw = 400, mh = 160) { + Popup.ui = ui; + init(mx, my, mw, mh); + + boxTitle = ""; + boxText = ""; + boxCommands = commands; + } + + static function init(mx = -1, my = -1, mw = 400, mh = 160) { + var appW = System.windowWidth(); + var appH = System.windowHeight(); + + modalX = mx; + modalY = my; + modalW = Std.int(mw * ui.SCALE()); + modalH = Std.int(mh * ui.SCALE()); + + // Center popup window if no value is given + if (mx == -1) modalX = Std.int(appW / 2 - modalW / 2); + if (my == -1) modalY = Std.int(appH / 2 - modalH / 2); + + // Limit popup position to screen + modalX = Std.int(Math.max(0, Math.min(modalX, appW - modalW))); + modalY = Std.int(Math.max(0, Math.min(modalY, appH - modalH))); + + hwnd.dragX = 0; + hwnd.dragY = 0; + hwnd.scrollOffset = 0.0; + show = true; + } +} From 30b2093b942497cb0da597f83e52bf677027be6b Mon Sep 17 00:00:00 2001 From: Lubos Lenco Date: Fri, 7 May 2021 17:02:31 +0200 Subject: [PATCH 119/264] Move game theme to armory.ui --- Sources/armory/trait/internal/CanvasScript.hx | 2 +- Sources/armory/ui/Themes.hx | 42 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 Sources/armory/ui/Themes.hx diff --git a/Sources/armory/trait/internal/CanvasScript.hx b/Sources/armory/trait/internal/CanvasScript.hx index f8c6d82f..21dab963 100644 --- a/Sources/armory/trait/internal/CanvasScript.hx +++ b/Sources/armory/trait/internal/CanvasScript.hx @@ -37,7 +37,7 @@ class CanvasScript extends Trait { } if (Canvas.themes.length == 0) { - Canvas.themes.push(zui.Themes.light); + Canvas.themes.push(armory.ui.Themes.light); } iron.data.Data.getFont(font, function(f: kha.Font) { diff --git a/Sources/armory/ui/Themes.hx b/Sources/armory/ui/Themes.hx new file mode 100644 index 00000000..34d397a9 --- /dev/null +++ b/Sources/armory/ui/Themes.hx @@ -0,0 +1,42 @@ +package armory.ui; + +import zui.Themes; + +class Themes { + + // 2x scaled, for games + public static var light: TTheme = { + NAME: "Default Light", + WINDOW_BG_COL: 0xffefefef, + WINDOW_TINT_COL: 0xff222222, + ACCENT_COL: 0xffeeeeee, + ACCENT_HOVER_COL: 0xffbbbbbb, + ACCENT_SELECT_COL: 0xffaaaaaa, + BUTTON_COL: 0xffcccccc, + BUTTON_TEXT_COL: 0xff222222, + BUTTON_HOVER_COL: 0xffb3b3b3, + BUTTON_PRESSED_COL: 0xffb1b1b1, + TEXT_COL: 0xff999999, + LABEL_COL: 0xffaaaaaa, + SEPARATOR_COL: 0xff999999, + HIGHLIGHT_COL: 0xff205d9c, + CONTEXT_COL: 0xffaaaaaa, + PANEL_BG_COL: 0xffaaaaaa, + FONT_SIZE: 13 * 2, + ELEMENT_W: 100 * 2, + ELEMENT_H: 24 * 2, + ELEMENT_OFFSET: 4 * 2, + ARROW_SIZE: 5 * 2, + BUTTON_H: 22 * 2, + CHECK_SIZE: 15 * 2, + CHECK_SELECT_SIZE: 8 * 2, + SCROLL_W: 6 * 2, + TEXT_OFFSET: 8 * 2, + TAB_W: 12 * 2, + FILL_WINDOW_BG: false, + FILL_BUTTON_BG: true, + FILL_ACCENT_BG: false, + LINK_STYLE: Line, + FULL_TABS: false + }; +} From 590afcc573309918c616483c1dc521a78b14b01e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 8 May 2021 23:09:45 +0200 Subject: [PATCH 120/264] Fix double-export of linked objects (#2175) --- blender/arm/exporter.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/blender/arm/exporter.py b/blender/arm/exporter.py index 33459a62..46698577 100755 --- a/blender/arm/exporter.py +++ b/blender/arm/exporter.py @@ -1594,9 +1594,11 @@ Make sure the mesh only has tris/quads.""") asset_name = arm.utils.asset_name(bobject) + # Collection is in the same file if collection.library is None: - # collection is in the same file, but (likely) on another scene - if asset_name not in scene_objects: + # Only export linked objects (from other scenes for example), + # all other objects (in scene_objects) are already exported. + if bobject.name not in scene_objects: self.process_bobject(bobject) self.export_object(bobject, self.scene) else: From 1945439cd1a271b6d229eb0c7e584cccb479ede0 Mon Sep 17 00:00:00 2001 From: N8n5h Date: Fri, 14 May 2021 11:12:46 -0300 Subject: [PATCH 121/264] Small improvements for the Shadow Map Atlas Blender UI * Made shadow map atlas sizes enum's values dynamic: This allows reducing issues related to picking a "wrong" size for the atlases. * Added a legend below shadow map sizes to have an idea of how much lights can an option fit. * Separated listing of subdivisions to include point lights. * Disable cubemap size if single map is enabled to show that cascade size option is only used. --- blender/arm/props_renderpath.py | 63 +++++++++++-------- blender/arm/props_ui.py | 103 ++++++++++++++++++++++++++------ 2 files changed, 123 insertions(+), 43 deletions(-) diff --git a/blender/arm/props_renderpath.py b/blender/arm/props_renderpath.py index 246c505c..cd7db3ea 100644 --- a/blender/arm/props_renderpath.py +++ b/blender/arm/props_renderpath.py @@ -4,6 +4,37 @@ from bpy.props import * import arm.assets as assets import arm.utils +atlas_sizes = [ ('256', '256', '256'), + ('512', '512', '512'), + ('1024', '1024', '1024'), + ('2048', '2048', '2048'), + ('4096', '4096', '4096'), + ('8192', '8192', '8192'), + ('16384', '16384', '16384'), + ('32768', '32768', '32768') ] + +def atlas_sizes_from_min(min_size: int) -> list: + """ Create an enum list of atlas sizes from a minimal size """ + sizes = [] + for i in range(len(atlas_sizes)): + if int(atlas_sizes[i][0]) > min_size: + sizes.append(atlas_sizes[i]) + return sizes + +def update_spot_sun_atlas_size_options(scene: bpy.types.Scene, context: bpy.types.Context) -> list: + wrd = bpy.data.worlds['Arm'] + if len(wrd.arm_rplist) <= wrd.arm_rplist_index: + return [] + rpdat = wrd.arm_rplist[wrd.arm_rplist_index] + return atlas_sizes_from_min(int(rpdat.rp_shadowmap_cascade)) + +def update_point_atlas_size_options(scene: bpy.types.Scene, context: bpy.types.Context) -> list: + wrd = bpy.data.worlds['Arm'] + if len(wrd.arm_rplist) <= wrd.arm_rplist_index: + return [] + rpdat = wrd.arm_rplist[wrd.arm_rplist_index] + return atlas_sizes_from_min(int(rpdat.rp_shadowmap_cube) * 2) + def update_preset(self, context): rpdat = arm.utils.get_rp() if self.rp_preset == 'Desktop': @@ -271,33 +302,17 @@ class ArmRPListItem(bpy.types.PropertyGroup): ('8', '8', '8'),], name="LOD Subdivisions", description="Number of subdivisions of the default tile size for LOD", default='2', update=update_renderpath) rp_shadowmap_atlas_max_size_point: EnumProperty( - items=[('1024', '1024', '1024'), - ('2048', '2048', '2048'), - ('4096', '4096', '4096'), - ('8192', '8192', '8192'), - ('16384', '16384', '16384')], - name="Max Atlas Texture Size Points", description="Sets the limit of the size of the texture.", default='8192', update=update_renderpath) + items=update_point_atlas_size_options, + name="Max Atlas Texture Size Points", description="Sets the limit of the size of the texture.", update=update_renderpath) rp_shadowmap_atlas_max_size_spot: EnumProperty( - items=[('1024', '1024', '1024'), - ('2048', '2048', '2048'), - ('4096', '4096', '4096'), - ('8192', '8192', '8192'), - ('16384', '16384', '16384')], - name="Max Atlas Texture Size Spots", description="Sets the limit of the size of the texture.", default='8192', update=update_renderpath) + items=update_spot_sun_atlas_size_options, + name="Max Atlas Texture Size Spots", description="Sets the limit of the size of the texture.", update=update_renderpath) rp_shadowmap_atlas_max_size_sun: EnumProperty( - items=[('1024', '1024', '1024'), - ('2048', '2048', '2048'), - ('4096', '4096', '4096'), - ('8192', '8192', '8192'), - ('16384', '16384', '16384')], - name="Max Atlas Texture Size Sun", description="Sets the limit of the size of the texture.", default='8192', update=update_renderpath) + items=update_spot_sun_atlas_size_options, + name="Max Atlas Texture Size Sun", description="Sets the limit of the size of the texture.", update=update_renderpath) rp_shadowmap_atlas_max_size: EnumProperty( - items=[('1024', '1024', '1024'), - ('2048', '2048', '2048'), - ('4096', '4096', '4096'), - ('8192', '8192', '8192'), - ('16384', '16384', '16384')], - name="Max Atlas Texture Size", description="Sets the limit of the size of the texture.", default='8192', update=update_renderpath) + items=update_spot_sun_atlas_size_options, + name="Max Atlas Texture Size", description="Sets the limit of the size of the texture.", update=update_renderpath) rp_shadowmap_cube: EnumProperty( items=[('256', '256', '256'), ('512', '512', '512'), diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index 00b13940..880c7f9a 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -1332,6 +1332,20 @@ class ARM_PT_RenderPathShadowsPanel(bpy.types.Panel): 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 @@ -1342,7 +1356,9 @@ class ARM_PT_RenderPathShadowsPanel(bpy.types.Panel): rpdat = wrd.arm_rplist[wrd.arm_rplist_index] layout.enabled = rpdat.rp_shadows - layout.prop(rpdat, 'rp_shadowmap_cube') + 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() @@ -1367,39 +1383,88 @@ class ARM_PT_RenderPathShadowsPanel(bpy.types.Panel): 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: " + ', '.join(map(str, subdivs_list)) + 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 int(rpdat.rp_shadowmap_cascade) >= int(rpdat.rp_shadowmap_atlas_max_size): - print(rpdat.rp_shadowmap_atlas_max_size) - colatlas_warning = colatlas_single.row() - colatlas_warning.alignment = 'RIGHT' - colatlas_warning.label(text=f'Warning: {rpdat.rp_shadowmap_atlas_max_size} is too small for the shadowmap size: {rpdat.rp_shadowmap_cascade}', icon="ERROR") + 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 int(rpdat.rp_shadowmap_cascade) > int(rpdat.rp_shadowmap_atlas_max_size_spot): - colatlas_warning = colatlas_mixed.row() - colatlas_warning.alignment = 'RIGHT' - colatlas_warning.label(text=f'Warning: {rpdat.rp_shadowmap_atlas_max_size_spot} is too small for the shadowmap size: {rpdat.rp_shadowmap_cascade}', icon="ERROR") + + 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 int(rpdat.rp_shadowmap_cascade) >= int(rpdat.rp_shadowmap_atlas_max_size_point): - colatlas_warning = colatlas_mixed.row() - colatlas_warning.alignment = 'RIGHT' - colatlas_warning.label(text=f'Warning: {rpdat.rp_shadowmap_atlas_max_size_point} is too small for the shadowmap size: {rpdat.rp_shadowmap_cube}', icon="ERROR") + + 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 int(rpdat.rp_shadowmap_cascade) >= int(rpdat.rp_shadowmap_atlas_max_size_sun): - colatlas_warning = colatlas_mixed.row() - colatlas_warning.alignment = 'RIGHT' - colatlas_warning.label(text=f'Warning: {rpdat.rp_shadowmap_atlas_max_size_sun} is too small for the shadowmap size: {rpdat.rp_shadowmap_cascade}', icon="ERROR") + + 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" From 07ffe06c1daf37c06148d15493bbd25d183bf316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Tue, 18 May 2021 21:24:45 +0200 Subject: [PATCH 122/264] Live patch: move to dedicated module and use msgbus --- blender/arm/handlers.py | 31 ++------- blender/arm/live_patch.py | 134 ++++++++++++++++++++++++++++++++++++++ blender/arm/make.py | 36 +--------- 3 files changed, 143 insertions(+), 58 deletions(-) create mode 100644 blender/arm/live_patch.py diff --git a/blender/arm/handlers.py b/blender/arm/handlers.py index aa8460e8..364ae17e 100644 --- a/blender/arm/handlers.py +++ b/blender/arm/handlers.py @@ -6,13 +6,14 @@ import bpy from bpy.app.handlers import persistent import arm.api +import arm.live_patch as live_patch import arm.logicnode.arm_nodes as arm_nodes import arm.nodes_logic -import arm.make as make import arm.make_state as state import arm.props as props import arm.utils + @persistent def on_depsgraph_update_post(self): if state.proc_build != None: @@ -40,12 +41,10 @@ def on_depsgraph_update_post(self): # Send last operator to Krom wrd = bpy.data.worlds['Arm'] - if state.proc_play != None and \ - state.target == 'krom' and \ - wrd.arm_live_patch: + if state.proc_play is not None and state.target == 'krom' and wrd.arm_live_patch: ops = bpy.context.window_manager.operators - if len(ops) > 0 and ops[-1] != None: - send_operator(ops[-1]) + if len(ops) > 0 and ops[-1] is not None: + live_patch.on_operator(ops[-1].bl_idname) # Hacky solution to update armory props after operator executions last_operator = bpy.context.active_operator @@ -71,24 +70,6 @@ def on_operator_post(operator_id: str) -> None: target_obj.arm_rb_collision_filter_mask = source_obj.arm_rb_collision_filter_mask -def send_operator(op): - if hasattr(bpy.context, 'object') and bpy.context.object != None: - obj = bpy.context.object.name - if op.name == 'Move': - vec = bpy.context.object.location - js = 'var o = iron.Scene.active.getChild("' + obj + '"); o.transform.loc.set(' + str(vec[0]) + ', ' + str(vec[1]) + ', ' + str(vec[2]) + '); o.transform.dirty = true;' - make.write_patch(js) - elif op.name == 'Resize': - vec = bpy.context.object.scale - js = 'var o = iron.Scene.active.getChild("' + obj + '"); o.transform.scale.set(' + str(vec[0]) + ', ' + str(vec[1]) + ', ' + str(vec[2]) + '); o.transform.dirty = true;' - make.write_patch(js) - elif op.name == 'Rotate': - vec = bpy.context.object.rotation_euler.to_quaternion() - js = 'var o = iron.Scene.active.getChild("' + obj + '"); o.transform.rot.set(' + str(vec[1]) + ', ' + str(vec[2]) + ', ' + str(vec[3]) + ' ,' + str(vec[0]) + '); o.transform.dirty = true;' - make.write_patch(js) - else: # Rebuild - make.patch() - def always(): # Force ui redraw if state.redraw_ui and context_screen != None: @@ -102,9 +83,11 @@ def always(): space.node_tree.arm_cached = False return 0.5 + appended_py_paths = [] context_screen = None + @persistent def on_load_post(context): global appended_py_paths diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py new file mode 100644 index 00000000..1273b28e --- /dev/null +++ b/blender/arm/live_patch.py @@ -0,0 +1,134 @@ +import os +import shutil +from typing import Type + +import bpy + +import arm.assets as assets +from arm.exporter import ArmoryExporter +import arm.log as log +import arm.make as make +import arm.make_state as state +import arm.utils + +# Current patch id +patch_id = 0 + +# Any object can act as a message bus owner +msgbus_owner = object() + + +def start(): + log.debug("Live patch session started") + + listen(bpy.types.Object, "location", "obj_location") + listen(bpy.types.Object, "rotation_euler", "obj_rotation") + listen(bpy.types.Object, "scale", "obj_scale") + + +def patch_export(): + """Re-export the current scene and update the game accordingly.""" + if state.proc_build is not None: + return + + assets.invalidate_enabled = False + fp = arm.utils.get_fp() + + with arm.utils.WorkingDir(fp): + asset_path = arm.utils.get_fp_build() + '/compiled/Assets/' + arm.utils.safestr(bpy.context.scene.name) + '.arm' + ArmoryExporter.export_scene(bpy.context, asset_path, scene=bpy.context.scene) + + if not os.path.isdir(arm.utils.build_dir() + '/compiled/Shaders/std'): + raw_shaders_path = arm.utils.get_sdk_path() + '/armory/Shaders/' + shutil.copytree(raw_shaders_path + 'std', arm.utils.build_dir() + '/compiled/Shaders/std') + node_path = arm.utils.get_node_path() + khamake_path = arm.utils.get_khamake_path() + + cmd = [ + node_path, khamake_path, 'krom', + '--shaderversion', '330', + '--parallelAssetConversion', '4', + '--to', arm.utils.build_dir() + '/debug', + '--nohaxe', + '--noproject' + ] + + assets.invalidate_enabled = True + state.proc_build = make.run_proc(cmd, patch_done) + + +def patch_done(): + """Signal Iron to reload the running scene after a re-export.""" + js = 'iron.Scene.patch();' + write_patch(js) + state.proc_build = None + bpy.msgbus.clear_by_owner(msgbus_owner) + + +def write_patch(js: str): + """Write the given javascript code to 'krom.patch'.""" + global patch_id + with open(arm.utils.get_fp_build() + '/debug/krom/krom.patch', 'w') as f: + patch_id += 1 + f.write(str(patch_id) + '\n') + f.write(js) + + +def listen(rna_type: Type[bpy.types.Struct], prop: str, event_id: str): + """Subscribe to '.'. The event_id can be choosen + freely but must match with the id used in send_event(). + """ + bpy.msgbus.subscribe_rna( + key=(rna_type, prop), + owner=msgbus_owner, + args=(event_id, ), + notify=send_event + # options={"PERSISTENT"} + ) + + +def send_event(event_id: str): + """Send the result of the given event to Krom.""" + if hasattr(bpy.context, 'object') and bpy.context.object is not None: + obj = bpy.context.object.name + + if bpy.context.object.mode == "OBJECT": + if event_id == "obj_location": + vec = bpy.context.object.location + js = f'var o = iron.Scene.active.getChild("{obj}"); o.transform.loc.set({vec[0]}, {vec[1]}, {vec[2]}); o.transform.dirty = true;' + write_patch(js) + + elif event_id == 'obj_scale': + vec = bpy.context.object.scale + js = f'var o = iron.Scene.active.getChild("{obj}"); o.transform.scale.set({vec[0]}, {vec[1]}, {vec[2]}); o.transform.dirty = true;' + write_patch(js) + + elif event_id == 'obj_rotation': + vec = bpy.context.object.rotation_euler.to_quaternion() + js = f'var o = iron.Scene.active.getChild("{obj}"); o.transform.rot.set({vec[1]}, {vec[2]}, {vec[3]}, {vec[0]}); o.transform.dirty = true;' + write_patch(js) + + else: + patch_export() + + +def on_operator(operator_id: str): + """As long as bpy.msgbus doesn't listen to changes made by + operators (*), additionally notify the callback manually. + + (*) https://developer.blender.org/T72109 + """ + # Don't re-export the scene for the following operators + if operator_id in ("VIEW3D_OT_select", "OUTLINER_OT_item_activate", "OBJECT_OT_editmode_toggle"): + return + + if operator_id == "TRANSFORM_OT_translate": + send_event("obj_location") + elif operator_id == "TRANSFORM_OT_rotate": + send_event("obj_rotation") + elif operator_id == "TRANSFORM_OT_resize": + send_event("obj_scale") + + # Rebuild + else: + patch_export() diff --git a/blender/arm/make.py b/blender/arm/make.py index 001eb2d4..cb40c320 100755 --- a/blender/arm/make.py +++ b/blender/arm/make.py @@ -17,6 +17,7 @@ import arm.assets as assets from arm.exporter import ArmoryExporter import arm.lib.make_datas import arm.lib.server +import arm.live_patch as live_patch import arm.log as log import arm.make_logic as make_logic import arm.make_renderpath as make_renderpath @@ -445,40 +446,6 @@ def build_done(): else: log.error('Build failed, check console') -def patch(): - if state.proc_build != None: - return - assets.invalidate_enabled = False - fp = arm.utils.get_fp() - os.chdir(fp) - asset_path = arm.utils.get_fp_build() + '/compiled/Assets/' + arm.utils.safestr(bpy.context.scene.name) + '.arm' - ArmoryExporter.export_scene(bpy.context, asset_path, scene=bpy.context.scene) - if not os.path.isdir(arm.utils.build_dir() + '/compiled/Shaders/std'): - raw_shaders_path = arm.utils.get_sdk_path() + '/armory/Shaders/' - shutil.copytree(raw_shaders_path + 'std', arm.utils.build_dir() + '/compiled/Shaders/std') - node_path = arm.utils.get_node_path() - khamake_path = arm.utils.get_khamake_path() - - cmd = [node_path, khamake_path, 'krom'] - cmd.extend(('--shaderversion', '330', '--parallelAssetConversion', '4', - '--to', arm.utils.build_dir() + '/debug', '--nohaxe', '--noproject')) - - assets.invalidate_enabled = True - state.proc_build = run_proc(cmd, patch_done) - -def patch_done(): - js = 'iron.Scene.patch();' - write_patch(js) - state.proc_build = None - -patch_id = 0 - -def write_patch(js): - global patch_id - with open(arm.utils.get_fp_build() + '/debug/krom/krom.patch', 'w') as f: - patch_id += 1 - f.write(str(patch_id) + '\n') - f.write(js) def runtime_to_target(): wrd = bpy.data.worlds['Arm'] @@ -545,6 +512,7 @@ def build_success(): webbrowser.open(html5_app_path) elif wrd.arm_runtime == 'Krom': if wrd.arm_live_patch: + live_patch.start() open(arm.utils.get_fp_build() + '/debug/krom/krom.patch', 'w').close() krom_location, krom_path = arm.utils.krom_paths() os.chdir(krom_location) From 68825516c9acb3ee6f14d7080e894f14bea9e3f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Tue, 18 May 2021 21:31:02 +0200 Subject: [PATCH 123/264] Live patch: more robust shader paths --- blender/arm/live_patch.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index 1273b28e..68000255 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -38,12 +38,13 @@ def patch_export(): asset_path = arm.utils.get_fp_build() + '/compiled/Assets/' + arm.utils.safestr(bpy.context.scene.name) + '.arm' ArmoryExporter.export_scene(bpy.context, asset_path, scene=bpy.context.scene) - if not os.path.isdir(arm.utils.build_dir() + '/compiled/Shaders/std'): - raw_shaders_path = arm.utils.get_sdk_path() + '/armory/Shaders/' - shutil.copytree(raw_shaders_path + 'std', arm.utils.build_dir() + '/compiled/Shaders/std') + dir_std_shaders_dst = os.path.join(arm.utils.build_dir(), 'compiled', 'Shaders', 'std') + if not os.path.isdir(dir_std_shaders_dst): + dir_std_shaders_src = os.path.join(arm.utils.get_sdk_path(), 'armory', 'Shaders', 'std') + shutil.copytree(dir_std_shaders_src, dir_std_shaders_dst) + node_path = arm.utils.get_node_path() khamake_path = arm.utils.get_khamake_path() - cmd = [ node_path, khamake_path, 'krom', '--shaderversion', '330', From 3d469105305f1aeeb9ac3c20c01f2fc78f301213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Tue, 18 May 2021 21:34:46 +0200 Subject: [PATCH 124/264] Live patch: don't re-export on trackball rotation (double 'r' key) --- blender/arm/live_patch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index 68000255..f5f4ff1c 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -125,7 +125,7 @@ def on_operator(operator_id: str): if operator_id == "TRANSFORM_OT_translate": send_event("obj_location") - elif operator_id == "TRANSFORM_OT_rotate": + elif operator_id in ("TRANSFORM_OT_rotate", "TRANSFORM_OT_trackball"): send_event("obj_rotation") elif operator_id == "TRANSFORM_OT_resize": send_event("obj_scale") From 7b2961459c73e3b215acd4244526c6f43073d299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Wed, 19 May 2021 21:17:38 +0200 Subject: [PATCH 125/264] Live patch support for light strength and color --- blender/arm/live_patch.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index f5f4ff1c..73cb2ae9 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -25,6 +25,12 @@ def start(): listen(bpy.types.Object, "rotation_euler", "obj_rotation") listen(bpy.types.Object, "scale", "obj_scale") + # 'energy' is defined in sub classes only, also workaround for + # https://developer.blender.org/T88408 + for light_type in (bpy.types.AreaLight, bpy.types.PointLight, bpy.types.SpotLight, bpy.types.SunLight): + listen(light_type, "color", "light_color") + listen(light_type, "energy", "light_energy") + def patch_export(): """Re-export the current scene and update the game accordingly.""" @@ -109,6 +115,26 @@ def send_event(event_id: str): js = f'var o = iron.Scene.active.getChild("{obj}"); o.transform.rot.set({vec[1]}, {vec[2]}, {vec[3]}, {vec[0]}); o.transform.dirty = true;' write_patch(js) + elif event_id == 'light_color': + light: bpy.types.Light = bpy.context.object.data + vec = light.color + js = f'var lRaw = iron.Scene.active.getLight("{light.name}").data.raw; lRaw.color[0]={vec[0]}; lRaw.color[1]={vec[1]}; lRaw.color[2]={vec[2]};' + write_patch(js) + + elif event_id == 'light_energy': + light: bpy.types.Light = bpy.context.object.data + + # Align strength to Armory, see exporter.export_light() + # TODO: Use exporter.export_light() and simply reload all raw light data in Iron? + strength_fac = 1.0 + if light.type == 'SUN': + strength_fac = 0.325 + elif light.type in ('POINT', 'SPOT', 'AREA'): + strength_fac = 0.01 + + js = f'var lRaw = iron.Scene.active.getLight("{light.name}").data.raw; lRaw.strength={light.energy * strength_fac};' + write_patch(js) + else: patch_export() From 9fbc3d6cd4678032e5c87cd2fd4824fb572c4061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Wed, 19 May 2021 21:17:57 +0200 Subject: [PATCH 126/264] Small cleanup --- blender/arm/live_patch.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index 73cb2ae9..83546001 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -38,9 +38,8 @@ def patch_export(): return assets.invalidate_enabled = False - fp = arm.utils.get_fp() - with arm.utils.WorkingDir(fp): + with arm.utils.WorkingDir(arm.utils.get_fp()): asset_path = arm.utils.get_fp_build() + '/compiled/Assets/' + arm.utils.safestr(bpy.context.scene.name) + '.arm' ArmoryExporter.export_scene(bpy.context, asset_path, scene=bpy.context.scene) @@ -81,7 +80,7 @@ def write_patch(js: str): f.write(js) -def listen(rna_type: Type[bpy.types.Struct], prop: str, event_id: str): +def listen(rna_type: Type[bpy.types.bpy_struct], prop: str, event_id: str): """Subscribe to '.'. The event_id can be choosen freely but must match with the id used in send_event(). """ From 5ed5b8d6a756bab362704a5be31c86fac6ec532e Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Wed, 17 Feb 2021 19:55:18 +0100 Subject: [PATCH 127/264] Nodes for IK and FK fix Edit logic node code fkcommit30_04 Upgrade bone IK node. Add comments --- Sources/armory/logicnode/BoneFKNode.hx | 15 ++++--- Sources/armory/logicnode/BoneIKNode.hx | 23 +++++++++-- blender/arm/logicnode/animation/LN_bone_ik.py | 41 +++++++++++++++++-- 3 files changed, 66 insertions(+), 13 deletions(-) diff --git a/Sources/armory/logicnode/BoneFKNode.hx b/Sources/armory/logicnode/BoneFKNode.hx index 8d8cabb1..06541fe8 100644 --- a/Sources/armory/logicnode/BoneFKNode.hx +++ b/Sources/armory/logicnode/BoneFKNode.hx @@ -28,17 +28,20 @@ class BoneFKNode extends LogicNode { // Manipulating bone in world space var bone = anim.getBone(boneName); - m = anim.getBoneMat(bone); - w = anim.getAbsMat(bone); + /* m = anim.getBoneMat(bone); + w = anim.getAbsMat(bone); */ function moveBone() { - m.setFrom(w); + /* m.setFrom(w); m.multmat(transform); iw.getInverse(w); - m.multmat(iw); + m.multmat(iw); */ + //trace("Perform FK"); + anim.setBoneMatFromWorldMat(transform.clone(), bone); - // anim.removeUpdate(moveBone); - // notified = false; + anim.removeUpdate(moveBone); + //trace("FK removed"); + notified = false; } if (!notified) { diff --git a/Sources/armory/logicnode/BoneIKNode.hx b/Sources/armory/logicnode/BoneIKNode.hx index de187f0a..c22d77c7 100644 --- a/Sources/armory/logicnode/BoneIKNode.hx +++ b/Sources/armory/logicnode/BoneIKNode.hx @@ -7,6 +7,13 @@ import iron.math.Vec4; class BoneIKNode extends LogicNode { var goal: Vec4; + var pole: Vec4; + var poleEnabled: Bool; + var chainLength: Int; + var maxIterartions: Int; + var precision: Float; + var rollAngle: Float; + var notified = false; public function new(tree: LogicTree) { @@ -19,6 +26,12 @@ class BoneIKNode extends LogicNode { var object: Object = inputs[1].get(); var boneName: String = inputs[2].get(); goal = inputs[3].get(); + poleEnabled = inputs[4].get(); + pole = inputs[5].get(); + chainLength = inputs[6].get(); + maxIterartions = inputs[7].get(); + precision = inputs[8].get(); + rollAngle = inputs[9].get(); if (object == null || goal == null) return; var anim = object.animation != null ? cast(object.animation, BoneAnimation) : null; @@ -26,11 +39,13 @@ class BoneIKNode extends LogicNode { var bone = anim.getBone(boneName); - function solveBone() { - anim.solveIK(bone, goal); + if(! poleEnabled) pole = null; - // anim.removeUpdate(solveBone); - // notified = false; + function solveBone() { + anim.solveIK(bone, goal, precision, maxIterartions, chainLength, pole, rollAngle); + + anim.removeUpdate(solveBone); + notified = false; } if (!notified) { diff --git a/blender/arm/logicnode/animation/LN_bone_ik.py b/blender/arm/logicnode/animation/LN_bone_ik.py index dd155015..83a55fc9 100644 --- a/blender/arm/logicnode/animation/LN_bone_ik.py +++ b/blender/arm/logicnode/animation/LN_bone_ik.py @@ -1,10 +1,29 @@ from arm.logicnode.arm_nodes import * class BoneIKNode(ArmLogicTreeNode): - """Applies inverse kinematics in the given object bone.""" + """Performs inverse kinematics on the selected armature with specified bone. + + @input Object: Armature on which IK should be performed. + + @input Bone: Effector or tip bone for the inverse kinematics + + @input Goal Position: Position in world coordinates the effector bone will track to + + @input Enable Pole: Bend IK solution towards pole location + + @input Pole Position: Location of the pole in world coordinates + + @input Chain Length: Number of bones to include in the IK solver including the effector. If set to 0, all bones from effector to the root bone of the armature will be considered. + + @input Max Iterations: Maximum allowed FABRIK iterations to solve for IK. For longer chains, more iterations are needed. + + @input Precision: Presition of IK to stop at. It is described as a tolerence in length. Typically 0.01 is a good value. + + @input Roll Angle: Roll the bones along their local axis with specified radians. set 0 for no extra roll. + """ bl_idname = 'LNBoneIKNode' bl_label = 'Bone IK' - arm_version = 1 + arm_version = 2 arm_section = 'armature' def init(self, context): @@ -12,6 +31,22 @@ class BoneIKNode(ArmLogicTreeNode): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('NodeSocketString', 'Bone') - self.add_input('NodeSocketVector', 'Goal') + self.add_input('NodeSocketVector', 'Goal Position') + self.add_input('NodeSocketBool', 'Enable Pole') + self.add_input('NodeSocketVector', 'Pole Position') + self.add_input('NodeSocketInt', 'Chain Length') + self.add_input('NodeSocketInt', 'Max Iterations', 10) + self.add_input('NodeSocketFloat', 'Precision', 0.01) + self.add_input('NodeSocketFloat', 'Roll Angle') + self.add_output('ArmNodeSocketAction', 'Out') + + def get_replacement_node(self, node_tree: bpy.types.NodeTree): + if self.arm_version not in (0, 1): + raise LookupError() + + return NodeReplacement( + 'LNBoneIKNode', self.arm_version, 'LNBoneIKNode', 2, + in_socket_mapping={0:0, 1:1, 2:2, 3:3}, out_socket_mapping={0:0} + ) From 646d3a74cf617dddbf6779f77e739c9ac6d0d8ed Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Thu, 20 May 2021 18:04:18 +0200 Subject: [PATCH 128/264] Create new node to get if bones are FK IK --- .../logicnode/animation/LN_get_bone_fk_ik_only.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 blender/arm/logicnode/animation/LN_get_bone_fk_ik_only.py diff --git a/blender/arm/logicnode/animation/LN_get_bone_fk_ik_only.py b/blender/arm/logicnode/animation/LN_get_bone_fk_ik_only.py new file mode 100644 index 00000000..165b946d --- /dev/null +++ b/blender/arm/logicnode/animation/LN_get_bone_fk_ik_only.py @@ -0,0 +1,14 @@ +from arm.logicnode.arm_nodes import * + +class GetBoneFkIkOnlyNode(ArmLogicTreeNode): + """Get if a particular bone is animated by Forward kinematics or Inverse kinematics only.""" + bl_idname = 'LNGetBoneFkIkOnlyNode' + bl_label = 'Get Bone FK IK Only' + arm_version = 1 + arm_section = 'armature' + + def init(self, context): + super(GetBoneFkIkOnlyNode, self).init(context) + self.add_input('ArmNodeSocketObject', 'Object') + self.add_input('NodeSocketString', 'Bone') + self.add_output('NodeSocketBool', 'FK or IK only') \ No newline at end of file From 86316a10bd64bffd8bb3b4baca85b03c85ea32b0 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Thu, 20 May 2021 18:04:45 +0200 Subject: [PATCH 129/264] Create new node to get world transforms of bones --- .../logicnode/animation/LN_get_bone_transform.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 blender/arm/logicnode/animation/LN_get_bone_transform.py diff --git a/blender/arm/logicnode/animation/LN_get_bone_transform.py b/blender/arm/logicnode/animation/LN_get_bone_transform.py new file mode 100644 index 00000000..0d21ea23 --- /dev/null +++ b/blender/arm/logicnode/animation/LN_get_bone_transform.py @@ -0,0 +1,14 @@ +from arm.logicnode.arm_nodes import * + +class GetBoneTransformNode(ArmLogicTreeNode): + """Returns bone transform in world space.""" + bl_idname = 'LNGetBoneTransformNode' + bl_label = 'Get Bone Transform' + arm_version = 1 + arm_section = 'armature' + + def init(self, context): + super(GetBoneTransformNode, self).init(context) + self.add_input('ArmNodeSocketObject', 'Object') + self.add_input('NodeSocketString', 'Bone') + self.add_output('NodeSocketShader', 'Transform') \ No newline at end of file From e7c1855b81f1803c44cf353ac658ed0f2f38d0aa Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Thu, 20 May 2021 18:05:35 +0200 Subject: [PATCH 130/264] Create new node to set if bones are FK IK only --- .../animation/LN_set_bone_fk_ik_only.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 blender/arm/logicnode/animation/LN_set_bone_fk_ik_only.py diff --git a/blender/arm/logicnode/animation/LN_set_bone_fk_ik_only.py b/blender/arm/logicnode/animation/LN_set_bone_fk_ik_only.py new file mode 100644 index 00000000..2594183a --- /dev/null +++ b/blender/arm/logicnode/animation/LN_set_bone_fk_ik_only.py @@ -0,0 +1,17 @@ +from arm.logicnode.arm_nodes import * + +class SetBoneFkIkOnlyNode(ArmLogicTreeNode): + """Set particular bone to be animated by Forward kinematics or Inverse kinematics only. All other animations will be ignored""" + bl_idname = 'LNSetBoneFkIkOnlyNode' + bl_label = 'Set Bone FK IK Only' + arm_version = 1 + arm_section = 'armature' + + def init(self, context): + super(SetBoneFkIkOnlyNode, self).init(context) + self.add_input('ArmNodeSocketAction', 'In') + self.add_input('ArmNodeSocketObject', 'Object') + self.add_input('NodeSocketString', 'Bone') + self.add_input('NodeSocketBool', 'FK or IK only') + + self.add_output('ArmNodeSocketAction', 'Out') From 6ded7e61f4778f48c1cc70d19213913a17eec665 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Thu, 20 May 2021 18:06:45 +0200 Subject: [PATCH 131/264] Improve bone FK node to accept transform in world space --- Sources/armory/logicnode/BoneFKNode.hx | 28 +++++++++++++++++--------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/Sources/armory/logicnode/BoneFKNode.hx b/Sources/armory/logicnode/BoneFKNode.hx index 06541fe8..9d72c876 100644 --- a/Sources/armory/logicnode/BoneFKNode.hx +++ b/Sources/armory/logicnode/BoneFKNode.hx @@ -1,5 +1,7 @@ package armory.logicnode; +import iron.math.Quat; +import iron.math.Vec4; import iron.object.Object; import iron.object.BoneAnimation; import iron.math.Mat4; @@ -26,21 +28,27 @@ class BoneFKNode extends LogicNode { var anim = object.animation != null ? cast(object.animation, BoneAnimation) : null; if (anim == null) anim = object.getParentArmature(object.name); - // Manipulating bone in world space + // Get bone in armature var bone = anim.getBone(boneName); - /* m = anim.getBoneMat(bone); - w = anim.getAbsMat(bone); */ function moveBone() { - /* m.setFrom(w); - m.multmat(transform); - iw.getInverse(w); - m.multmat(iw); */ - //trace("Perform FK"); - anim.setBoneMatFromWorldMat(transform.clone(), bone); + + var t2 = Mat4.identity(); + var loc= new Vec4(); + var rot = new Quat(); + var scl = new Vec4(); + //Set scale to Armature scale. Bone scaling not yet implemented + t2.setFrom(transform); + t2.decompose(loc, rot, scl); + scl = object.transform.world.getScale(); + t2.compose(loc, rot, scl); + + //Set the bone local transform from world transform + anim.setBoneMatFromWorldMat(t2, bone); + + //Remove this method from animation loop after FK anim.removeUpdate(moveBone); - //trace("FK removed"); notified = false; } From 43e145c3dae472d780fc8be535d339d39d14be41 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Thu, 20 May 2021 18:07:09 +0200 Subject: [PATCH 132/264] Add comments to bone IK node --- Sources/armory/logicnode/BoneIKNode.hx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/armory/logicnode/BoneIKNode.hx b/Sources/armory/logicnode/BoneIKNode.hx index c22d77c7..1f1cfd76 100644 --- a/Sources/armory/logicnode/BoneIKNode.hx +++ b/Sources/armory/logicnode/BoneIKNode.hx @@ -42,8 +42,10 @@ class BoneIKNode extends LogicNode { if(! poleEnabled) pole = null; function solveBone() { + //Solve IK anim.solveIK(bone, goal, precision, maxIterartions, chainLength, pole, rollAngle); + //Remove this method from animation loop after IK anim.removeUpdate(solveBone); notified = false; } From c53d04374d651b2ad66a01624edc1447f63df25c Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Thu, 20 May 2021 18:07:46 +0200 Subject: [PATCH 133/264] Implement node to get if controlled by FK or IK --- .../armory/logicnode/GetBoneFkIkOnlyNode.hx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 Sources/armory/logicnode/GetBoneFkIkOnlyNode.hx diff --git a/Sources/armory/logicnode/GetBoneFkIkOnlyNode.hx b/Sources/armory/logicnode/GetBoneFkIkOnlyNode.hx new file mode 100644 index 00000000..14c96489 --- /dev/null +++ b/Sources/armory/logicnode/GetBoneFkIkOnlyNode.hx @@ -0,0 +1,31 @@ +package armory.logicnode; + +import iron.object.Object; +import iron.object.BoneAnimation; + +class GetBoneFkIkOnlyNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Bool { + #if arm_skin + + var object: Object = inputs[0].get(); + var boneName: String = inputs[1].get(); + + if (object == null) return null; + var anim = object.animation != null ? cast(object.animation, BoneAnimation) : null; + if (anim == null) anim = object.getParentArmature(object.name); + + // Get bone in armature + var bone = anim.getBone(boneName); + + //Get bone transform in world coordinates + return bone.is_IK_FK_only; + + + #end + } +} From 03baa49ecdd6b5160303dda27344e01a77fa5cfd Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Thu, 20 May 2021 18:08:11 +0200 Subject: [PATCH 134/264] Implement node to get bone transform in world space --- .../armory/logicnode/GetBoneTransformNode.hx | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 Sources/armory/logicnode/GetBoneTransformNode.hx diff --git a/Sources/armory/logicnode/GetBoneTransformNode.hx b/Sources/armory/logicnode/GetBoneTransformNode.hx new file mode 100644 index 00000000..098fd519 --- /dev/null +++ b/Sources/armory/logicnode/GetBoneTransformNode.hx @@ -0,0 +1,30 @@ +package armory.logicnode; + +import iron.object.Object; +import iron.object.BoneAnimation; +import iron.math.Mat4; + +class GetBoneTransformNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Mat4 { + #if arm_skin + + var object: Object = inputs[0].get(); + var boneName: String = inputs[1].get(); + + if (object == null) return null; + var anim = object.animation != null ? cast(object.animation, BoneAnimation) : null; + if (anim == null) anim = object.getParentArmature(object.name); + + // Get bone in armature + var bone = anim.getBone(boneName); + + return anim.getAbsWorldMat(bone); + + #end + } +} From a35cab548200de7b67c4d0aae2a1e49833540974 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Thu, 20 May 2021 18:08:43 +0200 Subject: [PATCH 135/264] Implement node to set bone animated by FK or IK only --- .../armory/logicnode/SetBoneFkIkOnlyNode.hx | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 Sources/armory/logicnode/SetBoneFkIkOnlyNode.hx diff --git a/Sources/armory/logicnode/SetBoneFkIkOnlyNode.hx b/Sources/armory/logicnode/SetBoneFkIkOnlyNode.hx new file mode 100644 index 00000000..927fc84e --- /dev/null +++ b/Sources/armory/logicnode/SetBoneFkIkOnlyNode.hx @@ -0,0 +1,35 @@ +package armory.logicnode; + +import iron.math.Quat; +import iron.math.Vec4; +import iron.object.Object; +import iron.object.BoneAnimation; + +class SetBoneFkIkOnlyNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + #if arm_skin + + var object: Object = inputs[1].get(); + var boneName: String = inputs[2].get(); + var fk_ik_only: Bool = inputs[3].get(); + + if (object == null) return; + var anim = object.animation != null ? cast(object.animation, BoneAnimation) : null; + if (anim == null) anim = object.getParentArmature(object.name); + + // Get bone in armature + var bone = anim.getBone(boneName); + + //Set bone animated by FK or IK only + bone.is_IK_FK_only = fk_ik_only; + + runOutput(0); + + #end + } +} From 088bc0f666738a4ddf89e1e69c7c00bd05f4f1c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 20 May 2021 20:38:43 +0200 Subject: [PATCH 136/264] Exporter: remove no longer used Blender version checks --- blender/arm/exporter.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/blender/arm/exporter.py b/blender/arm/exporter.py index ed408ae7..c177ebdc 100755 --- a/blender/arm/exporter.py +++ b/blender/arm/exporter.py @@ -1531,24 +1531,18 @@ Make sure the mesh only has tris/quads.""") # Less bias for bigger maps out_light['shadows_bias'] *= 1 / (out_light['shadowmap_size'] / 1024) elif objtype == 'POINT': - out_light['strength'] *= 2.6 - if bpy.app.version >= (2, 80, 72): - out_light['strength'] *= 0.01 + out_light['strength'] *= 0.01 out_light['fov'] = 1.5708 # pi/2 out_light['shadowmap_cube'] = True if light_ref.shadow_soft_size > 0.1: out_light['light_size'] = light_ref.shadow_soft_size * 10 elif objtype == 'SPOT': - out_light['strength'] *= 2.6 - if bpy.app.version >= (2, 80, 72): - out_light['strength'] *= 0.01 + out_light['strength'] *= 0.01 out_light['spot_size'] = math.cos(light_ref.spot_size / 2) # Cycles defaults to 0.15 out_light['spot_blend'] = light_ref.spot_blend / 10 elif objtype == 'AREA': - out_light['strength'] *= 80.0 / (light_ref.size * light_ref.size_y) - if bpy.app.version >= (2, 80, 72): - out_light['strength'] *= 0.01 + out_light['strength'] *= 0.01 out_light['size'] = light_ref.size out_light['size_y'] = light_ref.size_y From 20c6c52ae64823673435e2f93f1b10f2f4e57641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 22 May 2021 00:36:58 +0200 Subject: [PATCH 137/264] Merge node: add execution mode and output for input index --- Sources/armory/logicnode/MergeNode.hx | 20 +++++++++++++ blender/arm/logicnode/logic/LN_merge.py | 37 +++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/Sources/armory/logicnode/MergeNode.hx b/Sources/armory/logicnode/MergeNode.hx index 60eb6ca8..c2a22e6a 100644 --- a/Sources/armory/logicnode/MergeNode.hx +++ b/Sources/armory/logicnode/MergeNode.hx @@ -2,11 +2,31 @@ package armory.logicnode; class MergeNode extends LogicNode { + /** Execution mode. **/ + public var property0: String; + + var lastInputIndex = -1; + public function new(tree: LogicTree) { super(tree); + tree.notifyOnLateUpdate(lateUpdate); } override function run(from: Int) { + // Check if there already were executions on the same frame + if (lastInputIndex != -1 && property0 == "once_per_frame") { + return; + } + + lastInputIndex = from; runOutput(0); } + + override function get(from: Int): Dynamic { + return lastInputIndex; + } + + function lateUpdate() { + lastInputIndex = -1; + } } diff --git a/blender/arm/logicnode/logic/LN_merge.py b/blender/arm/logicnode/logic/LN_merge.py index 5c3b8a9f..0f4e6544 100644 --- a/blender/arm/logicnode/logic/LN_merge.py +++ b/blender/arm/logicnode/logic/LN_merge.py @@ -1,7 +1,23 @@ from arm.logicnode.arm_nodes import * + class MergeNode(ArmLogicTreeNode): - """Activates the output when any connected input is activated. + """Activates the output when at least one connected input is activated. + If multiple inputs are active, the behaviour is specified by the + `Execution Mode` option. + + @output Active Input Index: [*Available if Execution Mode is set to + Once Per Input*] The index of the last input that activated the output, + -1 if there was no execution yet on the current frame. + + @option Execution Mode: The node's behaviour if multiple inputs are + active on the same frame. + + - `Once Per Input`: If multiple inputs are active on one frame, activate + the output for each active input individually (simple forwarding). + + - `Once Per Frame`: If multiple inputs are active on one frame, + trigger the output only once. @option New: Add a new input socket. @option X Button: Remove the lowermost input socket.""" @@ -10,6 +26,21 @@ class MergeNode(ArmLogicTreeNode): arm_section = 'flow' arm_version = 1 + def update_exec_mode(self, context): + self.outputs['Active Input Index'].hide = self.property0 == 'once_per_frame' + + property0: EnumProperty( + name='Execution Mode', + description='The node\'s behaviour if multiple inputs are active on the same frame', + items=[('once_per_input', 'Once Per Input', + 'If multiple inputs are active on one frame, activate the' + ' output for each active input individually (simple forwarding)'), + ('once_per_frame', 'Once Per Frame', + 'If multiple inputs are active on one frame, trigger the output only once')], + default='once_per_input', + update=update_exec_mode, + ) + def __init__(self): super(MergeNode, self).__init__() array_nodes[str(id(self))] = self @@ -17,10 +48,12 @@ class MergeNode(ArmLogicTreeNode): def init(self, context): super(MergeNode, self).init(context) self.add_output('ArmNodeSocketAction', 'Out') + self.add_output('NodeSocketInt', 'Active Input Index') def draw_buttons(self, context, layout): - row = layout.row(align=True) + layout.prop(self, 'property0', text='') + row = layout.row(align=True) op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True) op.node_index = str(id(self)) op.socket_type = 'ArmNodeSocketAction' From 494e2336ac22ce549b6dc85f431c7ba59610b8f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 22 May 2021 12:28:57 +0200 Subject: [PATCH 138/264] Node replacement: write original traceback to file also in case of update failures --- blender/arm/logicnode/replacement.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/blender/arm/logicnode/replacement.py b/blender/arm/logicnode/replacement.py index d062a7bc..abd016e5 100644 --- a/blender/arm/logicnode/replacement.py +++ b/blender/arm/logicnode/replacement.py @@ -254,7 +254,8 @@ def replace_all(): print(f"A node whose class doesn't exist was found in node tree \"{tree_name}\"", file=reportf) elif error_type == 'update failed': print(f"A node of type {node_class} in tree \"{tree_name}\" failed to be updated, " - f"because there is no (longer?) an update routine for this version of the node.", file=reportf) + f"because there is no (longer?) an update routine for this version of the node. Original exception:" + "\n" + tb + "\n", file=reportf) elif error_type == 'future version': print(f"A node of type {node_class} in tree \"{tree_name}\" seemingly comes from a future version of armory. " f"Please check whether your version of armory is up to date", file=reportf) From 11da953407b497fa9fe88dae02025910903ccdb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 22 May 2021 12:30:38 +0200 Subject: [PATCH 139/264] Merge node: add update routine --- blender/arm/logicnode/logic/LN_merge.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/blender/arm/logicnode/logic/LN_merge.py b/blender/arm/logicnode/logic/LN_merge.py index 0f4e6544..ce42ac80 100644 --- a/blender/arm/logicnode/logic/LN_merge.py +++ b/blender/arm/logicnode/logic/LN_merge.py @@ -24,7 +24,7 @@ class MergeNode(ArmLogicTreeNode): bl_idname = 'LNMergeNode' bl_label = 'Merge' arm_section = 'flow' - arm_version = 1 + arm_version = 2 def update_exec_mode(self, context): self.outputs['Active Input Index'].hide = self.property0 == 'once_per_frame' @@ -65,3 +65,24 @@ class MergeNode(ArmLogicTreeNode): return self.bl_label return f'{self.bl_label}: [{len(self.inputs)}]' + + def get_replacement_node(self, node_tree: bpy.types.NodeTree): + if self.arm_version not in (0, 1): + raise LookupError() + + newnode = node_tree.nodes.new('LNMergeNode') + newnode.property0 = self.property0 + + # Recreate all original inputs + array_nodes[str(id(newnode))] = newnode + for idx, input in enumerate(self.inputs): + bpy.ops.arm.node_add_input('EXEC_DEFAULT', node_index=str(id(newnode)), socket_type='ArmNodeSocketAction') + + for link in input.links: + node_tree.links.new(link.from_socket, newnode.inputs[idx]) + + # Recreate outputs + for link in self.outputs[0].links: + node_tree.links.new(newnode.outputs[0], link.to_socket) + + return newnode From 7b5294121d37ac6f12c07faf6efa2af09c91fe2b Mon Sep 17 00:00:00 2001 From: Lubos Lenco Date: Sun, 23 May 2021 22:19:48 +0200 Subject: [PATCH 140/264] is_IK_FK_only -> is_ik_fk_only --- Sources/armory/logicnode/SetBoneFkIkOnlyNode.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/armory/logicnode/SetBoneFkIkOnlyNode.hx b/Sources/armory/logicnode/SetBoneFkIkOnlyNode.hx index 927fc84e..4d9b59fb 100644 --- a/Sources/armory/logicnode/SetBoneFkIkOnlyNode.hx +++ b/Sources/armory/logicnode/SetBoneFkIkOnlyNode.hx @@ -26,7 +26,7 @@ class SetBoneFkIkOnlyNode extends LogicNode { var bone = anim.getBone(boneName); //Set bone animated by FK or IK only - bone.is_IK_FK_only = fk_ik_only; + bone.is_ik_fk_only = fk_ik_only; runOutput(0); From bdc2d91c1eb30c0981f125d99f018e4c033c8d07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Wed, 26 May 2021 12:42:11 +0200 Subject: [PATCH 141/264] Fix instancing on mobile and solid renderpaths `gl_Position` was set before the instancing code in the vertex shader, this was the same issue as already fixed for desktop renderpaths in https://github.com/armory3d/armory/pull/2141 --- blender/arm/material/make_mesh.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/blender/arm/material/make_mesh.py b/blender/arm/material/make_mesh.py index 00932ef0..275fa0e0 100644 --- a/blender/arm/material/make_mesh.py +++ b/blender/arm/material/make_mesh.py @@ -296,8 +296,6 @@ def make_forward_mobile(con_mesh): vert.write_attrib('vec4 spos = vec4(pos.xyz, 1.0);') frag.ins = vert.outs - make_attrib.write_vertpos(vert) - frag.add_include('compiled.inc') frag.write('vec3 basecol;') frag.write('float roughness;') @@ -344,6 +342,8 @@ def make_forward_mobile(con_mesh): make_attrib.write_norpos(con_mesh, vert) frag.write_attrib('vec3 n = normalize(wnormal);') + make_attrib.write_vertpos(vert) + frag.add_include('std/math.glsl') frag.add_include('std/brdf.glsl') @@ -474,8 +474,6 @@ def make_forward_solid(con_mesh): vert.write_attrib('vec4 spos = vec4(pos.xyz, 1.0);') frag.ins = vert.outs - make_attrib.write_vertpos(vert) - frag.add_include('compiled.inc') frag.write('vec3 basecol;') frag.write('float roughness;') @@ -512,6 +510,7 @@ def make_forward_solid(con_mesh): vert.write('vcolor = col.rgb;') make_attrib.write_norpos(con_mesh, vert, write_nor=False) + make_attrib.write_vertpos(vert) frag.add_out('vec4 fragColor') if blend and parse_opacity: From 7057ec4ba6bda06b0e60a694e6eabdeed48a60f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 27 May 2021 22:41:27 +0200 Subject: [PATCH 142/264] Fix mixing multiple cycles shaders in one material --- blender/arm/material/cycles.py | 2 ++ blender/arm/material/parser_state.py | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/blender/arm/material/cycles.py b/blender/arm/material/cycles.py index 1366b66f..20dbd83e 100644 --- a/blender/arm/material/cycles.py +++ b/blender/arm/material/cycles.py @@ -204,6 +204,8 @@ def parse_shader(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> Tuple[st 'BSDF_VELVET': nodes_shader.parse_bsdfvelvet, } + state.reset_outs() + if node.type in node_parser_funcs: node_parser_funcs[node.type](node, socket, state) diff --git a/blender/arm/material/parser_state.py b/blender/arm/material/parser_state.py index bcbb59c8..8606e63f 100644 --- a/blender/arm/material/parser_state.py +++ b/blender/arm/material/parser_state.py @@ -64,6 +64,16 @@ class ParserState: self.out_opacity: floatstr = '1.0' self.out_emission: floatstr = '0.0' + def reset_outs(self): + """Reset the shader output values to their default values.""" + self.out_basecol = 'vec3(0.8)' + self.out_roughness = '0.0' + self.out_metallic = '0.0' + self.out_occlusion = '1.0' + self.out_specular = '1.0' + self.out_opacity = '1.0' + self.out_emission = '0.0' + def get_outs(self) -> Tuple[vec3str, floatstr, floatstr, floatstr, floatstr, floatstr, floatstr]: """Return the shader output values as a tuple.""" return (self.out_basecol, self.out_roughness, self.out_metallic, self.out_occlusion, self.out_specular, From 6d370950147e8da030af0b70f57448b3101fd595 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 27 May 2021 22:56:07 +0200 Subject: [PATCH 143/264] Fix usage of layer weight node in shadowmap shaders --- blender/arm/material/make_depth.py | 2 -- blender/arm/material/make_finalize.py | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/blender/arm/material/make_depth.py b/blender/arm/material/make_depth.py index 03ae858d..aa1f0864 100644 --- a/blender/arm/material/make_depth.py +++ b/blender/arm/material/make_depth.py @@ -33,8 +33,6 @@ def make(context_id, rpasses, shadowmap=False): parse_custom_particle = (cycles.node_by_name(mat_state.nodes, 'ArmCustomParticleNode') is not None) if parse_opacity: - frag.write('vec3 n;') # Discard at compile time - frag.write('float dotNV;') frag.write('float opacity;') if con_depth.is_elem('bone'): diff --git a/blender/arm/material/make_finalize.py b/blender/arm/material/make_finalize.py index 1eca9455..d16d3d00 100644 --- a/blender/arm/material/make_finalize.py +++ b/blender/arm/material/make_finalize.py @@ -13,6 +13,23 @@ def make(con_mesh): if frag.contains('dotNV') and not frag.contains('float dotNV'): frag.write_init('float dotNV = max(dot(n, vVec), 0.0);') + # n is not always defined yet (in some shadowmap shaders e.g.) + if not frag.contains('vec3 n'): + vert.add_out('vec3 wnormal') + vert.add_uniform('mat3 N', '_normalMatrix') + vert.write_attrib('wnormal = normalize(N * vec3(nor.xy, pos.w));') + frag.write_attrib('vec3 n = normalize(wnormal);') + + # If not yet added, add nor vertex data + vertex_elems = con_mesh.data['vertex_elements'] + has_normals = False + for elem in vertex_elems: + if elem['name'] == 'nor': + has_normals = True + break + if not has_normals: + vertex_elems.append({'name': 'nor', 'data': 'short2norm'}) + write_wpos = False if frag.contains('vVec') and not frag.contains('vec3 vVec'): if tese != None: From ccc427c04a79d3ebec16049bc49723bd171bdfca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 27 May 2021 22:57:53 +0200 Subject: [PATCH 144/264] Cleanup --- Shaders/std/sky.glsl | 4 ++-- blender/arm/material/make_attrib.py | 4 +--- blender/arm/material/make_finalize.py | 17 ++++++++++------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Shaders/std/sky.glsl b/Shaders/std/sky.glsl index d3d04e53..b4465351 100644 --- a/Shaders/std/sky.glsl +++ b/Shaders/std/sky.glsl @@ -60,7 +60,7 @@ float random(vec2 coords) { vec3 nishita_lookupLUT(const float height, const float sunTheta) { vec2 coords = vec2( - sqrt(height * (1 / nishita_atmo_radius)), + sqrt(height * (1 / nishita_atmo_radius)), 0.5 + 0.5 * sign(sunTheta - HALF_PI) * sqrt(abs(sunTheta * (1 / HALF_PI) - 1)) ); return textureLod(nishitaLUT, coords, 0.0).rgb; @@ -125,7 +125,7 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa // Idea behind this: "Rotate" everything by iPos (-> iPos is the new zenith) and then all calculations for the // inner integral only depend on the sample height (iHeight) and sunTheta (angle between sun and new zenith). float sunTheta = acos(dot(normalize(iPos), normalize(pSun))); - vec3 jODepth = nishita_lookupLUT(iHeight, sunTheta);// * vec3(14000000 / 255, 14000000 / 255, 2000000 / 255); + vec3 jODepth = nishita_lookupLUT(iHeight, sunTheta); // Apply dithering to reduce visible banding jODepth += mix(-1000, 1000, random(r.xy)); diff --git a/blender/arm/material/make_attrib.py b/blender/arm/material/make_attrib.py index 1d01e99b..6749d854 100644 --- a/blender/arm/material/make_attrib.py +++ b/blender/arm/material/make_attrib.py @@ -34,13 +34,11 @@ def write_vertpos(vert): def write_norpos(con_mesh: shader.ShaderContext, vert: shader.Shader, declare=False, write_nor=True): - prep = '' - if declare: - prep = 'vec3 ' is_bone = con_mesh.is_elem('bone') if is_bone: make_skin.skin_pos(vert) if write_nor: + prep = 'vec3 ' if declare else '' if is_bone: make_skin.skin_nor(vert, prep) else: diff --git a/blender/arm/material/make_finalize.py b/blender/arm/material/make_finalize.py index d16d3d00..afeeee8f 100644 --- a/blender/arm/material/make_finalize.py +++ b/blender/arm/material/make_finalize.py @@ -1,7 +1,10 @@ import bpy -import arm.material.make_tess as make_tess -def make(con_mesh): +import arm.material.make_tess as make_tess +from arm.material.shader import ShaderContext + + +def make(con_mesh: ShaderContext): vert = con_mesh.vert frag = con_mesh.frag geom = con_mesh.geom @@ -32,7 +35,7 @@ def make(con_mesh): write_wpos = False if frag.contains('vVec') and not frag.contains('vec3 vVec'): - if tese != None: + if tese is not None: tese.add_out('vec3 eyeDir') tese.add_uniform('vec3 eye', '_cameraPosition') tese.write('eyeDir = eye - wposition;') @@ -48,7 +51,7 @@ def make(con_mesh): export_wpos = False if frag.contains('wposition') and not frag.contains('vec3 wposition'): export_wpos = True - if tese != None: + if tese is not None: export_wpos = True if vert.contains('wposition'): write_wpos = True @@ -67,7 +70,7 @@ def make(con_mesh): vert.add_uniform('float posUnpack', link='_posUnpack') vert.write_attrib('mposition = spos.xyz * posUnpack;') - if tese != None: + if tese is not None: if frag_mpos: make_tess.interpolate(tese, 'mposition', 3, declare_out=True) elif tese.contains('mposition') and not tese.contains('vec3 mposition'): @@ -89,7 +92,7 @@ def make(con_mesh): vert.write_attrib('if (dim.y == 0) bposition.y = 0;') vert.write_attrib('if (dim.x == 0) bposition.x = 0;') - if tese != None: + if tese is not None: if frag_bpos: make_tess.interpolate(tese, 'bposition', 3, declare_out=True) elif tese.contains('bposition') and not tese.contains('vec3 bposition'): @@ -110,7 +113,7 @@ def make(con_mesh): vert.write('wtangent = normalize(N * tang.xyz);') vert.write_pre = False - if tese != None: + if tese is not None: if frag_wtan: make_tess.interpolate(tese, 'wtangent', 3, declare_out=True) elif tese.contains('wtangent') and not tese.contains('vec3 wtangent'): From 5eb8fc781f51a726ed2e5e75e64b4f403bfa2269 Mon Sep 17 00:00:00 2001 From: Lubos Lenco Date: Wed, 2 Jun 2021 21:18:47 +0200 Subject: [PATCH 145/264] Bump version --- blender/arm/props.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/props.py b/blender/arm/props.py index 99497c80..2b23d334 100755 --- a/blender/arm/props.py +++ b/blender/arm/props.py @@ -11,7 +11,7 @@ import arm.proxy import arm.utils # Armory version -arm_version = '2021.5' +arm_version = '2021.6' arm_commit = '$Id$' def get_project_html5_copy(self): From 20f9f8a5f4b082e20a2aee198339c5e13c0ea2a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 5 Jun 2021 20:35:21 +0200 Subject: [PATCH 146/264] Add Select node Implements feature requests #2200 and #2201 --- Sources/armory/logicnode/SelectNode.hx | 37 +++++++ blender/arm/logicnode/arm_nodes.py | 46 +++++++-- blender/arm/logicnode/logic/LN_select.py | 122 +++++++++++++++++++++++ blender/arm/nodes_logic.py | 2 + 4 files changed, 198 insertions(+), 9 deletions(-) create mode 100644 Sources/armory/logicnode/SelectNode.hx create mode 100644 blender/arm/logicnode/logic/LN_select.py diff --git a/Sources/armory/logicnode/SelectNode.hx b/Sources/armory/logicnode/SelectNode.hx new file mode 100644 index 00000000..10ca5252 --- /dev/null +++ b/Sources/armory/logicnode/SelectNode.hx @@ -0,0 +1,37 @@ +package armory.logicnode; + +class SelectNode extends LogicNode { + + /** Execution mode. **/ + public var property0: String; + + var value: Dynamic = null; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + // Get value according to the activated input (run() can only be called + // if the execution mode is from_input). + value = inputs[from + Std.int(inputs.length / 2)].get(); + + runOutput(0); + } + + override function get(from: Int): Dynamic { + if (property0 == "from_index") { + var index = inputs[0].get() + 2; + + // Return default value for invalid index + if (index < 2 || index >= inputs.length) { + return inputs[1].get(); + } + + return inputs[index].get(); + } + + // from_input + return value; + } +} diff --git a/blender/arm/logicnode/arm_nodes.py b/blender/arm/logicnode/arm_nodes.py index e99b8bd2..7582f966 100644 --- a/blender/arm/logicnode/arm_nodes.py +++ b/blender/arm/logicnode/arm_nodes.py @@ -270,6 +270,30 @@ class ArmNodeRemoveInputOutputButton(bpy.types.Operator): return{'FINISHED'} +class ArmNodeCallFuncButton(bpy.types.Operator): + """Operator that calls a function on a specified + node (used for dynamic callbacks).""" + bl_idname = 'arm.node_call_func' + bl_label = 'Execute' + bl_options = {'UNDO', 'INTERNAL'} + + node_index: StringProperty(name='Node Index', default='') + callback_name: StringProperty(name='Callback Name', default='') + + def execute(self, context): + node = array_nodes[self.node_index] + if hasattr(node, self.callback_name): + getattr(node, self.callback_name)() + else: + return {'CANCELLED'} + + # Reset to default again for subsequent calls of this operator + self.node_index = '' + self.callback_name = '' + + return {'FINISHED'} + + class ArmNodeSearch(bpy.types.Operator): bl_idname = "arm.node_search" bl_label = "Search..." @@ -460,12 +484,16 @@ def reset_globals(): category_items = OrderedDict() -bpy.utils.register_class(ArmNodeSearch) -bpy.utils.register_class(ArmNodeAddInputButton) -bpy.utils.register_class(ArmNodeAddInputValueButton) -bpy.utils.register_class(ArmNodeRemoveInputButton) -bpy.utils.register_class(ArmNodeRemoveInputValueButton) -bpy.utils.register_class(ArmNodeAddOutputButton) -bpy.utils.register_class(ArmNodeRemoveOutputButton) -bpy.utils.register_class(ArmNodeAddInputOutputButton) -bpy.utils.register_class(ArmNodeRemoveInputOutputButton) +REG_CLASSES = ( + ArmNodeSearch, + ArmNodeAddInputButton, + ArmNodeAddInputValueButton, + ArmNodeRemoveInputButton, + ArmNodeRemoveInputValueButton, + ArmNodeAddOutputButton, + ArmNodeRemoveOutputButton, + ArmNodeAddInputOutputButton, + ArmNodeRemoveInputOutputButton, + ArmNodeCallFuncButton +) +register, unregister = bpy.utils.register_classes_factory(REG_CLASSES) diff --git a/blender/arm/logicnode/logic/LN_select.py b/blender/arm/logicnode/logic/LN_select.py new file mode 100644 index 00000000..4ea3762a --- /dev/null +++ b/blender/arm/logicnode/logic/LN_select.py @@ -0,0 +1,122 @@ +from bpy.types import NodeSocketInterfaceInt + +from arm.logicnode.arm_nodes import * + + +class SelectNode(ArmLogicTreeNode): + """Selects one of multiple values (of arbitrary types) based on some + input state. The exact behaviour of this node is specified by the + `Execution Mode` option (see below). + + @output Out: [*Available if Execution Mode is set to From Input*] + Activated after the node was executed. + + @output Value: The last selected value. This value is not reset + until the next execution of this node. + + @option Execution Mode: Specifies the condition that determines + what value to choose. + + - `From Index`: Select the value at the given index. If there is + no value at that index, the value plugged in to the + `Default` input is used instead (`null` if unconnected). + + - `From Input`: This mode uses input pairs of one action socket + and one value socket. Depending on which action socket is + activated, the associated value socket (the value with the + same index as the activated action input) is forwarded to + the `Value` output. + + @option New: Add a new value to the list of values. + @option X Button: Remove the value with the highest index.""" + bl_idname = 'LNSelectNode' + bl_label = 'Select' + arm_version = 1 + min_inputs = 2 + + def update_exec_mode(self, context): + self.set_mode() + + property0: EnumProperty( + name='Execution Mode', + description="The node's behaviour.", + items=[ + ('from_index', 'From Index', 'Choose the value from the given index'), + ('from_input', 'From Input', 'Choose the value with the same position as the active input')], + default='from_index', + update=update_exec_mode, + ) + + # The number of choices, NOT of individual inputs. This needs to be + # a property in order to be saved with each individual node + num_choices: IntProperty(default=1, min=0) + + def __init__(self): + super().__init__() + array_nodes[str(id(self))] = self + + def init(self, context): + super().init(context) + + self.set_mode() + + def set_mode(self): + self.inputs.clear() + self.outputs.clear() + + if self.property0 == 'from_index': + self.add_input('NodeSocketInt', 'Index') + self.add_input('NodeSocketShader', 'Default') + self.num_choices = 0 + + # from_input + else: + # We could also start with index 1 here, but we need to use + # 0 for the "from_index" mode and it makes the code simpler + # if we stick to the same convention for both exec modes + self.add_input('ArmNodeSocketAction', 'Input 0') + self.add_input('NodeSocketShader', 'Value 0') + self.num_choices = 1 + + self.add_output('ArmNodeSocketAction', 'Out') + + self.add_output('NodeSocketShader', 'Value') + + def draw_buttons(self, context, layout): + layout.prop(self, 'property0', text='') + + row = layout.row(align=True) + + op = row.operator('arm.node_call_func', text='New', icon='PLUS', emboss=True) + op.node_index = str(id(self)) + op.callback_name = 'add_input_func' + + op = row.operator('arm.node_call_func', text='', icon='X', emboss=True) + op.node_index = str(id(self)) + op.callback_name = 'remove_input_func' + + def add_input_func(self): + if self.property0 == 'from_input': + self.add_input('ArmNodeSocketAction', f'Input {self.num_choices}') + + # Move new action input up to the end of all other action inputs + self.inputs.move(from_index=len(self.inputs) - 1, to_index=self.num_choices) + + self.add_input('NodeSocketShader', f'Value {self.num_choices}') + + self.num_choices += 1 + + def remove_input_func(self): + if self.property0 == 'from_input': + if len(self.inputs) > self.min_inputs: + self.inputs.remove(self.inputs[self.num_choices - 1]) + + if len(self.inputs) > self.min_inputs: + self.inputs.remove(self.inputs[-1]) + self.num_choices -= 1 + + def draw_label(self) -> str: + if self.num_choices == 0: + return self.bl_label + + return f'{self.bl_label}: [{self.num_choices}]' diff --git a/blender/arm/nodes_logic.py b/blender/arm/nodes_logic.py index 12f605fb..87b73869 100755 --- a/blender/arm/nodes_logic.py +++ b/blender/arm/nodes_logic.py @@ -363,6 +363,7 @@ class ReplaceNodesOperator(bpy.types.Operator): def register(): + arm.logicnode.arm_nodes.register() arm.logicnode.arm_sockets.register() bpy.utils.register_class(ArmLogicTree) @@ -403,3 +404,4 @@ def unregister(): bpy.utils.register_class(ARM_MT_NodeAddOverride.overridden_menu) arm.logicnode.arm_sockets.unregister() + arm.logicnode.arm_nodes.unregister() From 107dc730f4dc6a9a6013136002ebc53f67c27fd5 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Fri, 21 May 2021 22:28:36 +0200 Subject: [PATCH 147/264] Create and implement once per frame node --- Sources/armory/logicnode/OncePerFrameNode.hx | 20 +++++++++++++++++++ .../arm/logicnode/logic/LN_once_per_frame.py | 16 +++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 Sources/armory/logicnode/OncePerFrameNode.hx create mode 100644 blender/arm/logicnode/logic/LN_once_per_frame.py diff --git a/Sources/armory/logicnode/OncePerFrameNode.hx b/Sources/armory/logicnode/OncePerFrameNode.hx new file mode 100644 index 00000000..0e8fe32b --- /dev/null +++ b/Sources/armory/logicnode/OncePerFrameNode.hx @@ -0,0 +1,20 @@ +package armory.logicnode; + +class OncePerFrameNode extends LogicNode { + + var c = false; + + public function new(tree: LogicTree) { + super(tree); + tree.notifyOnUpdate(update); + } + + override function run(from: Int) { + if(c) runOutput(0); + c = false; + } + + function update() { + c = true; + } +} diff --git a/blender/arm/logicnode/logic/LN_once_per_frame.py b/blender/arm/logicnode/logic/LN_once_per_frame.py new file mode 100644 index 00000000..b7dba2f1 --- /dev/null +++ b/blender/arm/logicnode/logic/LN_once_per_frame.py @@ -0,0 +1,16 @@ +from arm.logicnode.arm_nodes import * + + +class OncePerFrameNode(ArmLogicTreeNode): + """Activates the output only once per frame if receives one or more inputs in that frame + If there is no input, there will be no output""" + bl_idname = 'LNOncePerFrameNode' + bl_label = 'Once Per Frame' + arm_section = 'flow' + arm_version = 1 + + def init(self, context): + super(OncePerFrameNode, self).init(context) + self.add_input('ArmNodeSocketAction', 'In') + + self.add_output('ArmNodeSocketAction', 'Out') From 0e6ba5b0b0ec9db6b5076aec1cfd4214286882ca Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Mon, 7 Jun 2021 13:49:12 +0200 Subject: [PATCH 148/264] reverse call order --- Sources/armory/logicnode/OncePerFrameNode.hx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/armory/logicnode/OncePerFrameNode.hx b/Sources/armory/logicnode/OncePerFrameNode.hx index 0e8fe32b..0e2aadd5 100644 --- a/Sources/armory/logicnode/OncePerFrameNode.hx +++ b/Sources/armory/logicnode/OncePerFrameNode.hx @@ -10,8 +10,10 @@ class OncePerFrameNode extends LogicNode { } override function run(from: Int) { - if(c) runOutput(0); - c = false; + if(c) { + c = false; + runOutput(0); + } } function update() { From b2619828ebcd44c126ff30f0a09ca46617151e56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Wed, 9 Jun 2021 23:11:22 +0200 Subject: [PATCH 149/264] Fix rendering multiple movie textures Fixes https://github.com/armory3d/armory/issues/1562 --- Sources/armory/trait/internal/MovieTexture.hx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Sources/armory/trait/internal/MovieTexture.hx b/Sources/armory/trait/internal/MovieTexture.hx index 1a3dc03a..194d49d2 100644 --- a/Sources/armory/trait/internal/MovieTexture.hx +++ b/Sources/armory/trait/internal/MovieTexture.hx @@ -8,8 +8,7 @@ import iron.object.MeshObject; class MovieTexture extends Trait { var video: Video; - public static var image: Image; - public static var created = false; + var image: Image; var videoName: String; @@ -33,10 +32,7 @@ class MovieTexture extends Trait { this.videoName = videoName; - if (!created) { - created = true; - notifyOnInit(init); - } + notifyOnInit(init); } function init() { @@ -46,7 +42,7 @@ class MovieTexture extends Trait { image = Image.createRenderTarget(getPower2(video.width()), getPower2(video.height())); - var o = cast(object, iron.object.MeshObject); + var o = cast(object, MeshObject); o.materials[0].contexts[0].textures[0] = image; // Override diffuse texture notifyOnRender2D(render); }); From 251ad8e47eaafc6fedca6b9c3110b79b5864ebff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 10 Jun 2021 20:27:23 +0200 Subject: [PATCH 150/264] Cache and reuse movietexture render targets with same size --- Sources/armory/trait/internal/MovieTexture.hx | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/Sources/armory/trait/internal/MovieTexture.hx b/Sources/armory/trait/internal/MovieTexture.hx index 194d49d2..02933f60 100644 --- a/Sources/armory/trait/internal/MovieTexture.hx +++ b/Sources/armory/trait/internal/MovieTexture.hx @@ -7,6 +7,15 @@ import iron.object.MeshObject; class MovieTexture extends Trait { + /** + Caches all render targets used by this trait for re-use when having + multiple videos of the same size. The lookup only takes place on trait + initialization. + + Map layout: `[width => [height => image]]` + **/ + static var imageCache: Map> = new Map(); + var video: Video; var image: Image; @@ -40,7 +49,19 @@ class MovieTexture extends Trait { video = vid; video.play(true); - image = Image.createRenderTarget(getPower2(video.width()), getPower2(video.height())); + var w = getPower2(video.width()); + var h = getPower2(video.height()); + + // Lazily fill the outer map + var hMap: Map = imageCache[w]; + if (hMap == null) { + imageCache[w] = new Map(); + } + + image = imageCache[w][h]; + if (image == null) { + imageCache[w][h] = image = Image.createRenderTarget(w, h); + } var o = cast(object, MeshObject); o.materials[0].contexts[0].textures[0] = image; // Override diffuse texture From 3288c3dcf5c4068cc6082ebd5b23d088427b9b1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 10 Jun 2021 20:28:30 +0200 Subject: [PATCH 151/264] MovieTexture: add documentation --- Sources/armory/trait/internal/MovieTexture.hx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Sources/armory/trait/internal/MovieTexture.hx b/Sources/armory/trait/internal/MovieTexture.hx index 02933f60..3adee41e 100644 --- a/Sources/armory/trait/internal/MovieTexture.hx +++ b/Sources/armory/trait/internal/MovieTexture.hx @@ -2,9 +2,16 @@ package armory.trait.internal; import kha.Image; import kha.Video; + import iron.Trait; import iron.object.MeshObject; +/** + Replaces the diffuse texture of the first material of the trait's object + with a video texture. + + @see https://github.com/armory3d/armory_examples/tree/master/material_movie +**/ class MovieTexture extends Trait { /** From 1ece052aee02616f7a6e086f12eaf5120b5ec007 Mon Sep 17 00:00:00 2001 From: Henrique Date: Sun, 13 Jun 2021 21:38:15 -0300 Subject: [PATCH 152/264] Fix GetBoneFkIkOnly node --- Sources/armory/logicnode/GetBoneFkIkOnlyNode.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/armory/logicnode/GetBoneFkIkOnlyNode.hx b/Sources/armory/logicnode/GetBoneFkIkOnlyNode.hx index 14c96489..e27dffa3 100644 --- a/Sources/armory/logicnode/GetBoneFkIkOnlyNode.hx +++ b/Sources/armory/logicnode/GetBoneFkIkOnlyNode.hx @@ -23,7 +23,7 @@ class GetBoneFkIkOnlyNode extends LogicNode { var bone = anim.getBone(boneName); //Get bone transform in world coordinates - return bone.is_IK_FK_only; + return bone.is_ik_fk_only; #end From a5fa3445d9591adc4ee4aac9fd01c4bbdc520a85 Mon Sep 17 00:00:00 2001 From: Henrique Date: Sun, 13 Jun 2021 22:43:13 -0300 Subject: [PATCH 153/264] Add new input nodes --- .../armory/logicnode/GetGamepadStartedNode.hx | 33 +++++++++++++++++++ .../logicnode/GetKeyboardStartedNode.hx | 32 ++++++++++++++++++ .../armory/logicnode/GetMouseStartedNode.hx | 32 ++++++++++++++++++ .../logicnode/input/LN_get_gamepad_started.py | 15 +++++++++ .../input/LN_get_keyboard_started.py | 14 ++++++++ .../logicnode/input/LN_get_mouse_started.py | 14 ++++++++ 6 files changed, 140 insertions(+) create mode 100644 Sources/armory/logicnode/GetGamepadStartedNode.hx create mode 100644 Sources/armory/logicnode/GetKeyboardStartedNode.hx create mode 100644 Sources/armory/logicnode/GetMouseStartedNode.hx create mode 100644 blender/arm/logicnode/input/LN_get_gamepad_started.py create mode 100644 blender/arm/logicnode/input/LN_get_keyboard_started.py create mode 100644 blender/arm/logicnode/input/LN_get_mouse_started.py diff --git a/Sources/armory/logicnode/GetGamepadStartedNode.hx b/Sources/armory/logicnode/GetGamepadStartedNode.hx new file mode 100644 index 00000000..f94b9e59 --- /dev/null +++ b/Sources/armory/logicnode/GetGamepadStartedNode.hx @@ -0,0 +1,33 @@ +package armory.logicnode; + +import iron.system.Input; + +class GetGamepadStartedNode extends LogicNode { + + var buttonStarted: Null; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var g = Input.getGamepad(inputs[0].get()); + + buttonStarted = null; + + for (b in Gamepad.buttons) { + if (g.started(b)) { + buttonStarted = b; + break; + } + } + + if (buttonStarted != null) { + runOutput(0); + } + } + + override function get(from: Int) { + return buttonStarted; + } +} diff --git a/Sources/armory/logicnode/GetKeyboardStartedNode.hx b/Sources/armory/logicnode/GetKeyboardStartedNode.hx new file mode 100644 index 00000000..ebe752e5 --- /dev/null +++ b/Sources/armory/logicnode/GetKeyboardStartedNode.hx @@ -0,0 +1,32 @@ +package armory.logicnode; + +import iron.system.Input; + +class GetKeyboardStartedNode extends LogicNode { + + var kb = Input.getKeyboard(); + var keyStarted: Null; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + keyStarted = null; + + for (k in Keyboard.keys) { + if (kb.started(k)) { + keyStarted = k; + break; + } + } + + if (keyStarted != null) { + runOutput(0); + } + } + + override function get(from: Int) { + return keyStarted; + } +} diff --git a/Sources/armory/logicnode/GetMouseStartedNode.hx b/Sources/armory/logicnode/GetMouseStartedNode.hx new file mode 100644 index 00000000..6bdb0fd0 --- /dev/null +++ b/Sources/armory/logicnode/GetMouseStartedNode.hx @@ -0,0 +1,32 @@ +package armory.logicnode; + +import iron.system.Input; + +class GetKeyboardStartedNode extends LogicNode { + + var m = Input.getMouse(); + var buttonStarted: Null; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + buttonStarted = null; + + for (b in Mouse.buttons) { + if (m.started(b)) { + buttonStarted = b; + break; + } + } + + if (buttonStarted != null) { + runOutput(0); + } + } + + override function get(from: Int) { + return buttonStarted; + } +} diff --git a/blender/arm/logicnode/input/LN_get_gamepad_started.py b/blender/arm/logicnode/input/LN_get_gamepad_started.py new file mode 100644 index 00000000..edfbc040 --- /dev/null +++ b/blender/arm/logicnode/input/LN_get_gamepad_started.py @@ -0,0 +1,15 @@ +from arm.logicnode.arm_nodes import * + +class GetGamepadStartedNode(ArmLogicTreeNode): + """.""" + bl_idname = 'LNGetGamepadStartedNode' + bl_label = 'Get Gamepad Started' + arm_version = 1 + + def init(self, context): + super(GetGamepadStartedNode, self).init(context) + self.add_input('ArmNodeSocketAction', 'In') + self.add_input('NodeSocketInt', 'Index') + + self.add_output('ArmNodeSocketAction', 'Out') + self.add_output('NodeSocketString', 'Button') diff --git a/blender/arm/logicnode/input/LN_get_keyboard_started.py b/blender/arm/logicnode/input/LN_get_keyboard_started.py new file mode 100644 index 00000000..0f655b04 --- /dev/null +++ b/blender/arm/logicnode/input/LN_get_keyboard_started.py @@ -0,0 +1,14 @@ +from arm.logicnode.arm_nodes import * + +class GetKeyboardStartedNode(ArmLogicTreeNode): + """.""" + bl_idname = 'LNGetKeyboardStartedNode' + bl_label = 'Get Keyboard Started' + arm_version = 1 + + def init(self, context): + super(GetKeyboardStartedNode, self).init(context) + self.add_input('ArmNodeSocketAction', 'In') + + self.add_output('ArmNodeSocketAction', 'Out') + self.add_output('NodeSocketString', 'Key') diff --git a/blender/arm/logicnode/input/LN_get_mouse_started.py b/blender/arm/logicnode/input/LN_get_mouse_started.py new file mode 100644 index 00000000..368aa433 --- /dev/null +++ b/blender/arm/logicnode/input/LN_get_mouse_started.py @@ -0,0 +1,14 @@ +from arm.logicnode.arm_nodes import * + +class GetMouseStartedNode(ArmLogicTreeNode): + """.""" + bl_idname = 'LNGetMouseStartedNode' + bl_label = 'Get Mouse Started' + arm_version = 1 + + def init(self, context): + super(GetMouseStartedNode, self).init(context) + self.add_input('ArmNodeSocketAction', 'In') + + self.add_output('ArmNodeSocketAction', 'Out') + self.add_output('NodeSocketString', 'Button') From f45304ea10b7bb7798040ba74d515de356d9e89c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Tue, 15 Jun 2021 15:24:58 +0200 Subject: [PATCH 154/264] Fix compilation of some nodes --- Sources/armory/logicnode/GetMouseStartedNode.hx | 2 +- Sources/armory/logicnode/OnCanvasElementNode.hx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/armory/logicnode/GetMouseStartedNode.hx b/Sources/armory/logicnode/GetMouseStartedNode.hx index 6bdb0fd0..8f2d5f32 100644 --- a/Sources/armory/logicnode/GetMouseStartedNode.hx +++ b/Sources/armory/logicnode/GetMouseStartedNode.hx @@ -2,7 +2,7 @@ package armory.logicnode; import iron.system.Input; -class GetKeyboardStartedNode extends LogicNode { +class GetMouseStartedNode extends LogicNode { var m = Input.getMouse(); var buttonStarted: Null; diff --git a/Sources/armory/logicnode/OnCanvasElementNode.hx b/Sources/armory/logicnode/OnCanvasElementNode.hx index 8b3ff763..0fae83e5 100644 --- a/Sources/armory/logicnode/OnCanvasElementNode.hx +++ b/Sources/armory/logicnode/OnCanvasElementNode.hx @@ -4,7 +4,7 @@ import armory.trait.internal.CanvasScript; import iron.Scene; #if arm_ui -import zui.Canvas.Anchor; +import armory.ui.Canvas.Anchor; #end class OnCanvasElementNode extends LogicNode { From 3c1264378b56df69454e62da5adddcca82f8581f Mon Sep 17 00:00:00 2001 From: Henrique Date: Sat, 19 Jun 2021 13:39:18 -0300 Subject: [PATCH 155/264] Cleanup in ui Ext --- Sources/armory/ui/Ext.hx | 308 +++++++++++++++++++-------------------- 1 file changed, 154 insertions(+), 154 deletions(-) diff --git a/Sources/armory/ui/Ext.hx b/Sources/armory/ui/Ext.hx index 0104ddd5..1197ef46 100644 --- a/Sources/armory/ui/Ext.hx +++ b/Sources/armory/ui/Ext.hx @@ -172,160 +172,160 @@ class Ext { Keycodes can be found here: http://api.kha.tech/kha/input/KeyCode.html **/ static function keycodeToString(keycode: Int): String { - switch (keycode) { - case -1: return "None"; - case KeyCode.Unknown: return "Unknown"; - case KeyCode.Back: return "Back"; - case KeyCode.Cancel: return "Cancel"; - case KeyCode.Help: return "Help"; - case KeyCode.Backspace: return "Backspace"; - case KeyCode.Tab: return "Tab"; - case KeyCode.Clear: return "Clear"; - case KeyCode.Return: return "Return"; - case KeyCode.Shift: return "Shift"; - case KeyCode.Control: return "Ctrl"; - case KeyCode.Alt: return "Alt"; - case KeyCode.Pause: return "Pause"; - case KeyCode.CapsLock: return "CapsLock"; - case KeyCode.Kana: return "Kana"; - // case KeyCode.Hangul: return "Hangul"; // Hangul == Kana - case KeyCode.Eisu: return "Eisu"; - case KeyCode.Junja: return "Junja"; - case KeyCode.Final: return "Final"; - case KeyCode.Hanja: return "Hanja"; - // case KeyCode.Kanji: return "Kanji"; // Kanji == Hanja - case KeyCode.Escape: return "Esc"; - case KeyCode.Convert: return "Convert"; - case KeyCode.NonConvert: return "NonConvert"; - case KeyCode.Accept: return "Accept"; - case KeyCode.ModeChange: return "ModeChange"; - case KeyCode.Space: return "Space"; - case KeyCode.PageUp: return "PageUp"; - case KeyCode.PageDown: return "PageDown"; - case KeyCode.End: return "End"; - case KeyCode.Home: return "Home"; - case KeyCode.Left: return "Left"; - case KeyCode.Up: return "Up"; - case KeyCode.Right: return "Right"; - case KeyCode.Down: return "Down"; - case KeyCode.Select: return "Select"; - case KeyCode.Print: return "Print"; - case KeyCode.Execute: return "Execute"; - case KeyCode.PrintScreen: return "PrintScreen"; - case KeyCode.Insert: return "Insert"; - case KeyCode.Delete: return "Delete"; - case KeyCode.Colon: return "Colon"; - case KeyCode.Semicolon: return "Semicolon"; - case KeyCode.LessThan: return "LessThan"; - case KeyCode.Equals: return "Equals"; - case KeyCode.GreaterThan: return "GreaterThan"; - case KeyCode.QuestionMark: return "QuestionMark"; - case KeyCode.At: return "At"; - case KeyCode.Win: return "Win"; - case KeyCode.ContextMenu: return "ContextMenu"; - case KeyCode.Sleep: return "Sleep"; - case KeyCode.Numpad0: return "Numpad0"; - case KeyCode.Numpad1: return "Numpad1"; - case KeyCode.Numpad2: return "Numpad2"; - case KeyCode.Numpad3: return "Numpad3"; - case KeyCode.Numpad4: return "Numpad4"; - case KeyCode.Numpad5: return "Numpad5"; - case KeyCode.Numpad6: return "Numpad6"; - case KeyCode.Numpad7: return "Numpad7"; - case KeyCode.Numpad8: return "Numpad8"; - case KeyCode.Numpad9: return "Numpad9"; - case KeyCode.Multiply: return "Multiply"; - case KeyCode.Add: return "Add"; - case KeyCode.Separator: return "Separator"; - case KeyCode.Subtract: return "Subtract"; - case KeyCode.Decimal: return "Decimal"; - case KeyCode.Divide: return "Divide"; - case KeyCode.F1: return "F1"; - case KeyCode.F2: return "F2"; - case KeyCode.F3: return "F3"; - case KeyCode.F4: return "F4"; - case KeyCode.F5: return "F5"; - case KeyCode.F6: return "F6"; - case KeyCode.F7: return "F7"; - case KeyCode.F8: return "F8"; - case KeyCode.F9: return "F9"; - case KeyCode.F10: return "F10"; - case KeyCode.F11: return "F11"; - case KeyCode.F12: return "F12"; - case KeyCode.F13: return "F13"; - case KeyCode.F14: return "F14"; - case KeyCode.F15: return "F15"; - case KeyCode.F16: return "F16"; - case KeyCode.F17: return "F17"; - case KeyCode.F18: return "F18"; - case KeyCode.F19: return "F19"; - case KeyCode.F20: return "F20"; - case KeyCode.F21: return "F21"; - case KeyCode.F22: return "F22"; - case KeyCode.F23: return "F23"; - case KeyCode.F24: return "F24"; - case KeyCode.NumLock: return "NumLock"; - case KeyCode.ScrollLock: return "ScrollLock"; - case KeyCode.WinOemFjJisho: return "WinOemFjJisho"; - case KeyCode.WinOemFjMasshou: return "WinOemFjMasshou"; - case KeyCode.WinOemFjTouroku: return "WinOemFjTouroku"; - case KeyCode.WinOemFjLoya: return "WinOemFjLoya"; - case KeyCode.WinOemFjRoya: return "WinOemFjRoya"; - case KeyCode.Circumflex: return "Circumflex"; - case KeyCode.Exclamation: return "Exclamation"; - case KeyCode.DoubleQuote: return "DoubleQuote"; - case KeyCode.Hash: return "Hash"; - case KeyCode.Dollar: return "Dollar"; - case KeyCode.Percent: return "Percent"; - case KeyCode.Ampersand: return "Ampersand"; - case KeyCode.Underscore: return "Underscore"; - case KeyCode.OpenParen: return "OpenParen"; - case KeyCode.CloseParen: return "CloseParen"; - case KeyCode.Asterisk: return "Asterisk"; - case KeyCode.Plus: return "Plus"; - case KeyCode.Pipe: return "Pipe"; - case KeyCode.HyphenMinus: return "HyphenMinus"; - case KeyCode.OpenCurlyBracket: return "OpenCurlyBracket"; - case KeyCode.CloseCurlyBracket: return "CloseCurlyBracket"; - case KeyCode.Tilde: return "Tilde"; - case KeyCode.VolumeMute: return "VolumeMute"; - case KeyCode.VolumeDown: return "VolumeDown"; - case KeyCode.VolumeUp: return "VolumeUp"; - case KeyCode.Comma: return "Comma"; - case KeyCode.Period: return "Period"; - case KeyCode.Slash: return "Slash"; - case KeyCode.BackQuote: return "BackQuote"; - case KeyCode.OpenBracket: return "OpenBracket"; - case KeyCode.BackSlash: return "BackSlash"; - case KeyCode.CloseBracket: return "CloseBracket"; - case KeyCode.Quote: return "Quote"; - case KeyCode.Meta: return "Meta"; - case KeyCode.AltGr: return "AltGr"; - case KeyCode.WinIcoHelp: return "WinIcoHelp"; - case KeyCode.WinIco00: return "WinIco00"; - case KeyCode.WinIcoClear: return "WinIcoClear"; - case KeyCode.WinOemReset: return "WinOemReset"; - case KeyCode.WinOemJump: return "WinOemJump"; - case KeyCode.WinOemPA1: return "WinOemPA1"; - case KeyCode.WinOemPA2: return "WinOemPA2"; - case KeyCode.WinOemPA3: return "WinOemPA3"; - case KeyCode.WinOemWSCTRL: return "WinOemWSCTRL"; - case KeyCode.WinOemCUSEL: return "WinOemCUSEL"; - case KeyCode.WinOemATTN: return "WinOemATTN"; - case KeyCode.WinOemFinish: return "WinOemFinish"; - case KeyCode.WinOemCopy: return "WinOemCopy"; - case KeyCode.WinOemAuto: return "WinOemAuto"; - case KeyCode.WinOemENLW: return "WinOemENLW"; - case KeyCode.WinOemBackTab: return "WinOemBackTab"; - case KeyCode.ATTN: return "ATTN"; - case KeyCode.CRSEL: return "CRSEL"; - case KeyCode.EXSEL: return "EXSEL"; - case KeyCode.EREOF: return "EREOF"; - case KeyCode.Play: return "Play"; - case KeyCode.Zoom: return "Zoom"; - case KeyCode.PA1: return "PA1"; - case KeyCode.WinOemClear: return "WinOemClear"; + return switch (keycode) { + default: String.fromCharCode(keycode); + case -1: "None"; + case KeyCode.Unknown: "Unknown"; + case KeyCode.Back: "Back"; + case KeyCode.Cancel: "Cancel"; + case KeyCode.Help: "Help"; + case KeyCode.Backspace: "Backspace"; + case KeyCode.Tab: "Tab"; + case KeyCode.Clear: "Clear"; + case KeyCode.Return: "Return"; + case KeyCode.Shift: "Shift"; + case KeyCode.Control: "Ctrl"; + case KeyCode.Alt: "Alt"; + case KeyCode.Pause: "Pause"; + case KeyCode.CapsLock: "CapsLock"; + case KeyCode.Kana: "Kana"; + // case KeyCode.Hangul: "Hangul"; // Hangul == Kana + case KeyCode.Eisu: "Eisu"; + case KeyCode.Junja: "Junja"; + case KeyCode.Final: "Final"; + case KeyCode.Hanja: "Hanja"; + // case KeyCode.Kanji: "Kanji"; // Kanji == Hanja + case KeyCode.Escape: "Esc"; + case KeyCode.Convert: "Convert"; + case KeyCode.NonConvert: "NonConvert"; + case KeyCode.Accept: "Accept"; + case KeyCode.ModeChange: "ModeChange"; + case KeyCode.Space: "Space"; + case KeyCode.PageUp: "PageUp"; + case KeyCode.PageDown: "PageDown"; + case KeyCode.End: "End"; + case KeyCode.Home: "Home"; + case KeyCode.Left: "Left"; + case KeyCode.Up: "Up"; + case KeyCode.Right: "Right"; + case KeyCode.Down: "Down"; + case KeyCode.Select: "Select"; + case KeyCode.Print: "Print"; + case KeyCode.Execute: "Execute"; + case KeyCode.PrintScreen: "PrintScreen"; + case KeyCode.Insert: "Insert"; + case KeyCode.Delete: "Delete"; + case KeyCode.Colon: "Colon"; + case KeyCode.Semicolon: "Semicolon"; + case KeyCode.LessThan: "LessThan"; + case KeyCode.Equals: "Equals"; + case KeyCode.GreaterThan: "GreaterThan"; + case KeyCode.QuestionMark: "QuestionMark"; + case KeyCode.At: "At"; + case KeyCode.Win: "Win"; + case KeyCode.ContextMenu: "ContextMenu"; + case KeyCode.Sleep: "Sleep"; + case KeyCode.Numpad0: "Numpad0"; + case KeyCode.Numpad1: "Numpad1"; + case KeyCode.Numpad2: "Numpad2"; + case KeyCode.Numpad3: "Numpad3"; + case KeyCode.Numpad4: "Numpad4"; + case KeyCode.Numpad5: "Numpad5"; + case KeyCode.Numpad6: "Numpad6"; + case KeyCode.Numpad7: "Numpad7"; + case KeyCode.Numpad8: "Numpad8"; + case KeyCode.Numpad9: "Numpad9"; + case KeyCode.Multiply: "Multiply"; + case KeyCode.Add: "Add"; + case KeyCode.Separator: "Separator"; + case KeyCode.Subtract: "Subtract"; + case KeyCode.Decimal: "Decimal"; + case KeyCode.Divide: "Divide"; + case KeyCode.F1: "F1"; + case KeyCode.F2: "F2"; + case KeyCode.F3: "F3"; + case KeyCode.F4: "F4"; + case KeyCode.F5: "F5"; + case KeyCode.F6: "F6"; + case KeyCode.F7: "F7"; + case KeyCode.F8: "F8"; + case KeyCode.F9: "F9"; + case KeyCode.F10: "F10"; + case KeyCode.F11: "F11"; + case KeyCode.F12: "F12"; + case KeyCode.F13: "F13"; + case KeyCode.F14: "F14"; + case KeyCode.F15: "F15"; + case KeyCode.F16: "F16"; + case KeyCode.F17: "F17"; + case KeyCode.F18: "F18"; + case KeyCode.F19: "F19"; + case KeyCode.F20: "F20"; + case KeyCode.F21: "F21"; + case KeyCode.F22: "F22"; + case KeyCode.F23: "F23"; + case KeyCode.F24: "F24"; + case KeyCode.NumLock: "NumLock"; + case KeyCode.ScrollLock: "ScrollLock"; + case KeyCode.WinOemFjJisho: "WinOemFjJisho"; + case KeyCode.WinOemFjMasshou: "WinOemFjMasshou"; + case KeyCode.WinOemFjTouroku: "WinOemFjTouroku"; + case KeyCode.WinOemFjLoya: "WinOemFjLoya"; + case KeyCode.WinOemFjRoya: "WinOemFjRoya"; + case KeyCode.Circumflex: "Circumflex"; + case KeyCode.Exclamation: "Exclamation"; + case KeyCode.DoubleQuote: "DoubleQuote"; + case KeyCode.Hash: "Hash"; + case KeyCode.Dollar: "Dollar"; + case KeyCode.Percent: "Percent"; + case KeyCode.Ampersand: "Ampersand"; + case KeyCode.Underscore: "Underscore"; + case KeyCode.OpenParen: "OpenParen"; + case KeyCode.CloseParen: "CloseParen"; + case KeyCode.Asterisk: "Asterisk"; + case KeyCode.Plus: "Plus"; + case KeyCode.Pipe: "Pipe"; + case KeyCode.HyphenMinus: "HyphenMinus"; + case KeyCode.OpenCurlyBracket: "OpenCurlyBracket"; + case KeyCode.CloseCurlyBracket: "CloseCurlyBracket"; + case KeyCode.Tilde: "Tilde"; + case KeyCode.VolumeMute: "VolumeMute"; + case KeyCode.VolumeDown: "VolumeDown"; + case KeyCode.VolumeUp: "VolumeUp"; + case KeyCode.Comma: "Comma"; + case KeyCode.Period: "Period"; + case KeyCode.Slash: "Slash"; + case KeyCode.BackQuote: "BackQuote"; + case KeyCode.OpenBracket: "OpenBracket"; + case KeyCode.BackSlash: "BackSlash"; + case KeyCode.CloseBracket: "CloseBracket"; + case KeyCode.Quote: "Quote"; + case KeyCode.Meta: "Meta"; + case KeyCode.AltGr: "AltGr"; + case KeyCode.WinIcoHelp: "WinIcoHelp"; + case KeyCode.WinIco00: "WinIco00"; + case KeyCode.WinIcoClear: "WinIcoClear"; + case KeyCode.WinOemReset: "WinOemReset"; + case KeyCode.WinOemJump: "WinOemJump"; + case KeyCode.WinOemPA1: "WinOemPA1"; + case KeyCode.WinOemPA2: "WinOemPA2"; + case KeyCode.WinOemPA3: "WinOemPA3"; + case KeyCode.WinOemWSCTRL: "WinOemWSCTRL"; + case KeyCode.WinOemCUSEL: "WinOemCUSEL"; + case KeyCode.WinOemATTN: "WinOemATTN"; + case KeyCode.WinOemFinish: "WinOemFinish"; + case KeyCode.WinOemCopy: "WinOemCopy"; + case KeyCode.WinOemAuto: "WinOemAuto"; + case KeyCode.WinOemENLW: "WinOemENLW"; + case KeyCode.WinOemBackTab: "WinOemBackTab"; + case KeyCode.ATTN: "ATTN"; + case KeyCode.CRSEL: "CRSEL"; + case KeyCode.EXSEL: "EXSEL"; + case KeyCode.EREOF: "EREOF"; + case KeyCode.Play: "Play"; + case KeyCode.Zoom: "Zoom"; + case KeyCode.PA1: "PA1"; + case KeyCode.WinOemClear: "WinOemClear"; } - return String.fromCharCode(keycode); } } From 66856e7ecc8a41653d674af9054b6e4f1cf4835b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Wed, 23 Jun 2021 20:19:08 +0200 Subject: [PATCH 156/264] Build/publish: add poll() function to prevent exception when executing from outside of UI If there was no exporter, calling `bpy.ops.arm.publish_project()` would result in an exception before. Now the call is simply ignored and a "poll failed" message is emitted instead. --- blender/arm/props_ui.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index 880c7f9a..8086a3a7 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -626,7 +626,6 @@ class ARM_PT_ArmoryExporterPanel(bpy.types.Panel): row = layout.row(align=True) row.alignment = 'EXPAND' row.scale_y = 1.3 - row.enabled = wrd.arm_exporterlist_index >= 0 and len(wrd.arm_exporterlist) > 0 row.operator("arm.build_project", icon="MOD_BUILD") # row.operator("arm.patch_project") row.operator("arm.publish_project", icon="EXPORT") @@ -1053,10 +1052,15 @@ class ArmoryStopButton(bpy.types.Operator): return{'FINISHED'} class ArmoryBuildProjectButton(bpy.types.Operator): - '''Build and compile project''' + """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(): @@ -1093,10 +1097,15 @@ class ArmoryBuildProjectButton(bpy.types.Operator): return{'FINISHED'} class ArmoryPublishProjectButton(bpy.types.Operator): - '''Build project ready for publishing''' + """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(): From d18aede964337829aa5e92fdd6e02e8c7cd73202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 24 Jun 2021 14:59:32 +0200 Subject: [PATCH 157/264] run_proc: no thread in background mode and call `done` in main thread --- blender/arm/handlers.py | 21 +++++++++++++++++++++ blender/arm/make.py | 39 ++++++++++++++++++++++++++++++++++----- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/blender/arm/handlers.py b/blender/arm/handlers.py index aa8460e8..eaaecfb8 100644 --- a/blender/arm/handlers.py +++ b/blender/arm/handlers.py @@ -1,5 +1,6 @@ import importlib import os +import queue import sys import bpy @@ -102,6 +103,24 @@ def always(): space.node_tree.arm_cached = False return 0.5 + +def poll_threads() -> float: + """Polls the thread callback queue and if a thread has finished, it + is joined with the main thread and the corresponding callback is + executed in the main thread. + """ + try: + thread, callback = make.thread_callback_queue.get(block=False) + except queue.Empty: + return 0.25 + + thread.join() + callback() + + # Quickly check if another thread has finished + return 0.01 + + appended_py_paths = [] context_screen = None @@ -181,7 +200,9 @@ def register(): bpy.app.handlers.load_post.append(on_load_post) bpy.app.handlers.depsgraph_update_post.append(on_depsgraph_update_post) # bpy.app.handlers.undo_post.append(on_undo_post) + bpy.app.timers.register(always, persistent=True) + bpy.app.timers.register(poll_threads, persistent=True) if arm.utils.get_fp() != '': appended_py_paths = [] diff --git a/blender/arm/make.py b/blender/arm/make.py index 001eb2d4..d7049831 100755 --- a/blender/arm/make.py +++ b/blender/arm/make.py @@ -10,6 +10,8 @@ import webbrowser import shlex import errno import math +from typing import Callable +from queue import Queue import bpy @@ -28,15 +30,42 @@ import arm.write_data as write_data scripts_mtime = 0 # Monitor source changes profile_time = 0 -def run_proc(cmd, done): - def fn(p, done): - p.wait() - if done != None: +# Queue of threads and their done callbacks. Item format: [thread, done] +thread_callback_queue = Queue(maxsize=0) + + +def run_proc(cmd, done: Callable) -> subprocess.Popen: + """Creates a subprocess with the given command and returns it. + + If Blender is not running in background mode, a thread is spawned + that waits until the subprocess has finished executing to not freeze + the UI, otherwise (in background mode) execution is blocked until + the subprocess has finished. + + If `done` is not `None`, it is called afterwards in the main thread. + """ + use_thread = not bpy.app.background + + def wait_for_proc(proc: subprocess.Popen): + proc.wait() + + if use_thread: + # Put the done callback into the callback queue so that it + # can be received by a polling function in the main thread + thread_callback_queue.put([threading.current_thread(), done], block=True) + else: done() + p = subprocess.Popen(cmd) - threading.Thread(target=fn, args=(p, done)).start() + + if use_thread: + threading.Thread(target=wait_for_proc, args=(p,)).start() + else: + wait_for_proc(p) + return p + def compile_shader_pass(res, raw_shaders_path, shader_name, defs, make_variants): os.chdir(raw_shaders_path + '/' + shader_name) From fce97a5ddfebc33421a8c85d81a8207fdb599dcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 24 Jun 2021 15:03:30 +0200 Subject: [PATCH 158/264] Cleanup --- blender/arm/handlers.py | 7 ++++--- blender/arm/make.py | 11 +++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/blender/arm/handlers.py b/blender/arm/handlers.py index eaaecfb8..ccb8b274 100644 --- a/blender/arm/handlers.py +++ b/blender/arm/handlers.py @@ -90,16 +90,17 @@ def send_operator(op): else: # Rebuild make.patch() -def always(): + +def always() -> float: # Force ui redraw - if state.redraw_ui and context_screen != None: + if state.redraw_ui and context_screen is not None: for area in context_screen.areas: if area.type == 'VIEW_3D' or area.type == 'PROPERTIES': area.tag_redraw() state.redraw_ui = False # TODO: depsgraph.updates only triggers material trees space = arm.utils.logic_editor_space(context_screen) - if space != None: + if space is not None: space.node_tree.arm_cached = False return 0.5 diff --git a/blender/arm/make.py b/blender/arm/make.py index d7049831..0ac315b8 100755 --- a/blender/arm/make.py +++ b/blender/arm/make.py @@ -1,17 +1,16 @@ +import errno import glob import json import os +from queue import Queue +import shlex import shutil -import time import stat import subprocess import threading -import webbrowser -import shlex -import errno -import math +import time from typing import Callable -from queue import Queue +import webbrowser import bpy From dc34e48c521048782f0aa960b0a52b7d3bde878d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Thu, 24 Jun 2021 17:26:21 +0200 Subject: [PATCH 159/264] Unregister timers on exit/disabling addon --- blender/arm/handlers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/blender/arm/handlers.py b/blender/arm/handlers.py index ccb8b274..b5020e53 100644 --- a/blender/arm/handlers.py +++ b/blender/arm/handlers.py @@ -220,6 +220,9 @@ def register(): def unregister(): + bpy.app.timers.unregister(poll_threads) + bpy.app.timers.unregister(always) + bpy.app.handlers.load_post.remove(on_load_post) bpy.app.handlers.depsgraph_update_post.remove(on_depsgraph_update_post) # bpy.app.handlers.undo_post.remove(on_undo_post) From a588396deac39485b164d603e53c5db5db19d783 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Sat, 5 Jun 2021 15:10:29 +0200 Subject: [PATCH 160/264] Update set material value param node --- .../material/LN_set_material_value_param.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/blender/arm/logicnode/material/LN_set_material_value_param.py b/blender/arm/logicnode/material/LN_set_material_value_param.py index 426807d1..c3691673 100644 --- a/blender/arm/logicnode/material/LN_set_material_value_param.py +++ b/blender/arm/logicnode/material/LN_set_material_value_param.py @@ -1,17 +1,27 @@ from arm.logicnode.arm_nodes import * class SetMaterialValueParamNode(ArmLogicTreeNode): - """TO DO.""" + """Set a float value material parameter to the specified object""" bl_idname = 'LNSetMaterialValueParamNode' bl_label = 'Set Material Value Param' arm_section = 'params' - arm_version = 1 + arm_version = 2 def init(self, context): super(SetMaterialValueParamNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') + self.add_input('ArmNodeSocketObject', 'Object') self.add_input('NodeSocketShader', 'Material') self.add_input('NodeSocketString', 'Node') self.add_input('NodeSocketFloat', 'Float') self.add_output('ArmNodeSocketAction', 'Out') + + def get_replacement_node(self, node_tree: bpy.types.NodeTree): + if self.arm_version not in (0, 1): + raise LookupError() + + return NodeReplacement( + 'LNSetMaterialValueParamNode', self.arm_version, 'LNSetMaterialValueParamNode', 2, + in_socket_mapping={0:0, 1:2, 2:3, 3:4}, out_socket_mapping={0:0} + ) From 6cf3299ffe9f5df44fb39791c87e34e711b0be06 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Sat, 5 Jun 2021 15:11:06 +0200 Subject: [PATCH 161/264] Implement and upgrade set material value parameter node --- .../logicnode/SetMaterialValueParamNode.hx | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/Sources/armory/logicnode/SetMaterialValueParamNode.hx b/Sources/armory/logicnode/SetMaterialValueParamNode.hx index 40729a64..ed969ae5 100644 --- a/Sources/armory/logicnode/SetMaterialValueParamNode.hx +++ b/Sources/armory/logicnode/SetMaterialValueParamNode.hx @@ -6,7 +6,7 @@ import iron.object.Object; class SetMaterialValueParamNode extends LogicNode { static var registered = false; - static var map = new Map>>(); + static var map = new Map>>>(); public function new(tree: LogicTree) { super(tree); @@ -17,21 +17,36 @@ class SetMaterialValueParamNode extends LogicNode { } override function run(from: Int) { - var mat = inputs[1].get(); - if (mat == null) return; - var entry = map.get(mat); + var obj = inputs[1].get(); + var mat = inputs[2].get(); + if(obj == null) return; + + var matMap = map.get(obj); + if (matMap == null) { + matMap = new Map(); + map.set(obj, matMap); + } + + var entry = matMap.get(mat); if (entry == null) { entry = new Map(); - map.set(mat, entry); + matMap.set(mat, entry); } - entry.set(inputs[2].get(), inputs[3].get()); // Node name, value + + entry.set(inputs[3].get(), inputs[4].get()); // Node name, value runOutput(0); } static function floatLink(object: Object, mat: MaterialData, link: String): Null { + if(object == null) return null; if (mat == null) return null; - var entry = map.get(mat); + + var material = map.get(object); + if (material == null) return null; + + var entry = material.get(mat); if (entry == null) return null; + return entry.get(link); } } From a9f430c3742d6ae1c10bc744ac4f38944d72778e Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Mon, 7 Jun 2021 13:40:34 +0200 Subject: [PATCH 162/264] Add per object option --- .../logicnode/material/LN_set_material_value_param.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/blender/arm/logicnode/material/LN_set_material_value_param.py b/blender/arm/logicnode/material/LN_set_material_value_param.py index c3691673..d668c7f9 100644 --- a/blender/arm/logicnode/material/LN_set_material_value_param.py +++ b/blender/arm/logicnode/material/LN_set_material_value_param.py @@ -7,6 +7,12 @@ class SetMaterialValueParamNode(ArmLogicTreeNode): arm_section = 'params' arm_version = 2 + property0: BoolProperty( + name="Per Object", + description="Set property per object", + default=False + ) + def init(self, context): super(SetMaterialValueParamNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') @@ -16,6 +22,9 @@ class SetMaterialValueParamNode(ArmLogicTreeNode): self.add_input('NodeSocketFloat', 'Float') self.add_output('ArmNodeSocketAction', 'Out') + + def draw_buttons(self, context, layout): + layout.prop(self, 'property0') def get_replacement_node(self, node_tree: bpy.types.NodeTree): if self.arm_version not in (0, 1): From aaa21bc01966cb18599d1a71116c91a103751a5a Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Mon, 7 Jun 2021 13:40:54 +0200 Subject: [PATCH 163/264] implement per object option --- .../logicnode/SetMaterialValueParamNode.hx | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/Sources/armory/logicnode/SetMaterialValueParamNode.hx b/Sources/armory/logicnode/SetMaterialValueParamNode.hx index ed969ae5..ec33130b 100644 --- a/Sources/armory/logicnode/SetMaterialValueParamNode.hx +++ b/Sources/armory/logicnode/SetMaterialValueParamNode.hx @@ -1,10 +1,13 @@ package armory.logicnode; +import iron.Scene; import iron.data.MaterialData; import iron.object.Object; class SetMaterialValueParamNode extends LogicNode { + public var property0: Bool; // per object + static var registered = false; static var map = new Map>>>(); @@ -17,14 +20,20 @@ class SetMaterialValueParamNode extends LogicNode { } override function run(from: Int) { - var obj = inputs[1].get(); - var mat = inputs[2].get(); - if(obj == null) return; + var object = inputs[1].get(); + if(object == null) return; - var matMap = map.get(obj); + var mat = inputs[2].get(); + if(mat == null) return; + + if(! property0){ + object = Scene.active.root; + } + + var matMap = map.get(object); if (matMap == null) { matMap = new Map(); - map.set(obj, matMap); + map.set(object, matMap); } var entry = matMap.get(mat); @@ -41,6 +50,10 @@ class SetMaterialValueParamNode extends LogicNode { if(object == null) return null; if (mat == null) return null; + if(! map.exists(object)){ + object = Scene.active.root; + } + var material = map.get(object); if (material == null) return null; From f542dc00cabb7643b5a020fbb8f31308569085e5 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Wed, 9 Jun 2021 19:09:02 +0200 Subject: [PATCH 164/264] update similar nodes --- .../material/LN_set_material_image_param.py | 23 +++++++++++++++++-- .../material/LN_set_material_rgb_param.py | 23 +++++++++++++++++-- .../material/LN_set_material_value_param.py | 2 +- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/blender/arm/logicnode/material/LN_set_material_image_param.py b/blender/arm/logicnode/material/LN_set_material_image_param.py index 777794c2..d442dc9a 100644 --- a/blender/arm/logicnode/material/LN_set_material_image_param.py +++ b/blender/arm/logicnode/material/LN_set_material_image_param.py @@ -1,17 +1,36 @@ from arm.logicnode.arm_nodes import * class SetMaterialImageParamNode(ArmLogicTreeNode): - """TO DO.""" + """Set a image or texture value material parameter to the specified object. If `per object` is disabled, value will be set to all objects with this material""" bl_idname = 'LNSetMaterialImageParamNode' bl_label = 'Set Material Image Param' arm_section = 'params' - arm_version = 1 + arm_version = 2 + + property0: BoolProperty( + name="Per Object", + description="Set property per object", + default=False + ) def init(self, context): super(SetMaterialImageParamNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') + self.add_input('ArmNodeSocketObject', 'Object') self.add_input('NodeSocketShader', 'Material') self.add_input('NodeSocketString', 'Node') self.add_input('NodeSocketString', 'Image') self.add_output('ArmNodeSocketAction', 'Out') + + def draw_buttons(self, context, layout): + layout.prop(self, 'property0') + + def get_replacement_node(self, node_tree: bpy.types.NodeTree): + if self.arm_version not in (0, 1): + raise LookupError() + + return NodeReplacement( + 'LNSetMaterialImageParamNode', self.arm_version, 'LNSetMaterialImageParamNode', 2, + in_socket_mapping={0:0, 1:2, 2:3, 3:4}, out_socket_mapping={0:0} + ) diff --git a/blender/arm/logicnode/material/LN_set_material_rgb_param.py b/blender/arm/logicnode/material/LN_set_material_rgb_param.py index 34948c9d..52ef94b9 100644 --- a/blender/arm/logicnode/material/LN_set_material_rgb_param.py +++ b/blender/arm/logicnode/material/LN_set_material_rgb_param.py @@ -1,17 +1,36 @@ from arm.logicnode.arm_nodes import * class SetMaterialRgbParamNode(ArmLogicTreeNode): - """TO DO.""" + """Set a color or vector value material parameter to the specified object. If `per object` is disabled, value will be set to all objects with this material""" bl_idname = 'LNSetMaterialRgbParamNode' bl_label = 'Set Material RGB Param' arm_section = 'params' - arm_version = 1 + arm_version = 2 + + property0: BoolProperty( + name="Per Object", + description="Set property per object", + default=False + ) def init(self, context): super(SetMaterialRgbParamNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') + self.add_input('ArmNodeSocketObject', 'Object') self.add_input('NodeSocketShader', 'Material') self.add_input('NodeSocketString', 'Node') self.add_input('NodeSocketColor', 'Color') self.add_output('ArmNodeSocketAction', 'Out') + + def draw_buttons(self, context, layout): + layout.prop(self, 'property0') + + def get_replacement_node(self, node_tree: bpy.types.NodeTree): + if self.arm_version not in (0, 1): + raise LookupError() + + return NodeReplacement( + 'LNSetMaterialRgbParamNode', self.arm_version, 'LNSetMaterialRgbParamNode', 2, + in_socket_mapping={0:0, 1:2, 2:3, 3:4}, out_socket_mapping={0:0} + ) diff --git a/blender/arm/logicnode/material/LN_set_material_value_param.py b/blender/arm/logicnode/material/LN_set_material_value_param.py index d668c7f9..5e99f14a 100644 --- a/blender/arm/logicnode/material/LN_set_material_value_param.py +++ b/blender/arm/logicnode/material/LN_set_material_value_param.py @@ -1,7 +1,7 @@ from arm.logicnode.arm_nodes import * class SetMaterialValueParamNode(ArmLogicTreeNode): - """Set a float value material parameter to the specified object""" + """Set a float value material parameter to the specified object. If `per object` is disabled, value will be set to all objects with this material""" bl_idname = 'LNSetMaterialValueParamNode' bl_label = 'Set Material Value Param' arm_section = 'params' From dbd348ad5dec85185051b171a837de80cc019741 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Wed, 9 Jun 2021 19:09:24 +0200 Subject: [PATCH 165/264] update node scripts --- .../logicnode/SetMaterialImageParamNode.hx | 38 +++++++--------- .../logicnode/SetMaterialRgbParamNode.hx | 37 ++++++++-------- .../logicnode/SetMaterialValueParamNode.hx | 43 +++---------------- 3 files changed, 39 insertions(+), 79 deletions(-) diff --git a/Sources/armory/logicnode/SetMaterialImageParamNode.hx b/Sources/armory/logicnode/SetMaterialImageParamNode.hx index abee9941..eea685a5 100644 --- a/Sources/armory/logicnode/SetMaterialImageParamNode.hx +++ b/Sources/armory/logicnode/SetMaterialImageParamNode.hx @@ -1,40 +1,34 @@ package armory.logicnode; +import iron.Scene; import iron.data.MaterialData; import iron.object.Object; +import armory.trait.internal.UniformsManager; class SetMaterialImageParamNode extends LogicNode { - static var registered = false; - static var map = new Map>(); + public var property0: Bool; // per object + + var manager: UniformsManager; public function new(tree: LogicTree) { super(tree); - if (!registered) { - registered = true; - iron.object.Uniforms.externalTextureLinks.push(textureLink); - } + + manager = new UniformsManager(UniformType.Texture); } override function run(from: Int) { - var mat = inputs[1].get(); - if (mat == null) return; - var entry = map.get(mat); - if (entry == null) { - entry = new Map(); - map.set(mat, entry); + var object = inputs[1].get(); + if(object == null) return; + + var mat = inputs[2].get(); + if(mat == null) return; + + if(! property0){ + object = Scene.active.root; } - iron.data.Data.getImage(inputs[3].get(), function(image: kha.Image) { - entry.set(inputs[2].get(), image); // Node name, value - }); + manager.setTextureValue(mat, object, inputs[3].get(), inputs[4].get()); runOutput(0); } - - static function textureLink(object: Object, mat: MaterialData, link: String): kha.Image { - if (mat == null) return null; - var entry = map.get(mat); - if (entry == null) return null; - return entry.get(link); - } } diff --git a/Sources/armory/logicnode/SetMaterialRgbParamNode.hx b/Sources/armory/logicnode/SetMaterialRgbParamNode.hx index 2db7bbc9..802a758e 100644 --- a/Sources/armory/logicnode/SetMaterialRgbParamNode.hx +++ b/Sources/armory/logicnode/SetMaterialRgbParamNode.hx @@ -1,38 +1,35 @@ package armory.logicnode; +import iron.Scene; import iron.math.Vec4; import iron.data.MaterialData; import iron.object.Object; +import armory.trait.internal.UniformsManager; class SetMaterialRgbParamNode extends LogicNode { - static var registered = false; - static var map = new Map>(); + public var property0: Bool; // per object + + var manager: UniformsManager; public function new(tree: LogicTree) { super(tree); - if (!registered) { - registered = true; - iron.object.Uniforms.externalVec3Links.push(vec3Link); - } + + manager = new UniformsManager(UniformType.Vector); } override function run(from: Int) { - var mat = inputs[1].get(); - if (mat == null) return; - var entry = map.get(mat); - if (entry == null) { - entry = new Map(); - map.set(mat, entry); + var object = inputs[1].get(); + if(object == null) return; + + var mat = inputs[2].get(); + if(mat == null) return; + + if(! property0){ + object = Scene.active.root; } - entry.set(inputs[2].get(), inputs[3].get()); // Node name, value + + manager.setVec3Value(mat, object, inputs[3].get(), inputs[4].get()); runOutput(0); } - - static function vec3Link(object: Object, mat: MaterialData, link: String): iron.math.Vec4 { - if (mat == null) return null; - var entry = map.get(mat); - if (entry == null) return null; - return entry.get(link); - } } diff --git a/Sources/armory/logicnode/SetMaterialValueParamNode.hx b/Sources/armory/logicnode/SetMaterialValueParamNode.hx index ec33130b..ea8e60c3 100644 --- a/Sources/armory/logicnode/SetMaterialValueParamNode.hx +++ b/Sources/armory/logicnode/SetMaterialValueParamNode.hx @@ -3,20 +3,18 @@ package armory.logicnode; import iron.Scene; import iron.data.MaterialData; import iron.object.Object; +import armory.trait.internal.UniformsManager; class SetMaterialValueParamNode extends LogicNode { public var property0: Bool; // per object - - static var registered = false; - static var map = new Map>>>(); + + var manager: UniformsManager; public function new(tree: LogicTree) { super(tree); - if (!registered) { - registered = true; - iron.object.Uniforms.externalFloatLinks.push(floatLink); - } + + manager = new UniformsManager(UniformType.Float); } override function run(from: Int) { @@ -30,36 +28,7 @@ class SetMaterialValueParamNode extends LogicNode { object = Scene.active.root; } - var matMap = map.get(object); - if (matMap == null) { - matMap = new Map(); - map.set(object, matMap); - } - - var entry = matMap.get(mat); - if (entry == null) { - entry = new Map(); - matMap.set(mat, entry); - } - - entry.set(inputs[3].get(), inputs[4].get()); // Node name, value + manager.setFloatValue(mat, object, inputs[3].get(), inputs[4].get()); runOutput(0); } - - static function floatLink(object: Object, mat: MaterialData, link: String): Null { - if(object == null) return null; - if (mat == null) return null; - - if(! map.exists(object)){ - object = Scene.active.root; - } - - var material = map.get(object); - if (material == null) return null; - - var entry = material.get(mat); - if (entry == null) return null; - - return entry.get(link); - } } From e744fd901c3d37dcc270d8052598a0ef5f2ed19a Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Wed, 9 Jun 2021 19:09:45 +0200 Subject: [PATCH 166/264] create and implement uniforms manager remove node mapping Revert "remove node mapping" This reverts commit e70aa60e120e71236cba885bd7e0e5f1b6acf39d. --- .../armory/trait/internal/UniformsManager.hx | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 Sources/armory/trait/internal/UniformsManager.hx diff --git a/Sources/armory/trait/internal/UniformsManager.hx b/Sources/armory/trait/internal/UniformsManager.hx new file mode 100644 index 00000000..e5686f97 --- /dev/null +++ b/Sources/armory/trait/internal/UniformsManager.hx @@ -0,0 +1,161 @@ +package armory.trait.internal; + +import haxe.ds.Map; +import iron.math.Vec4; +import iron.data.MaterialData; +import iron.Scene; +import iron.object.Object; +import iron.object.Uniforms; + + +class UniformsManager{ + + static var floatsRegistered = false; + static var floatsMap = new Map>>>(); + + static var vectorsRegistered = false; + static var vectorsMap = new Map>>(); + + static var texturesRegistered = false; + static var texturesMap = new Map>>(); + + public function new(type: UniformType){ + switch (type){ + case Float:{ + if(! floatsRegistered){ + floatsRegistered = true; + Uniforms.externalFloatLinks.push(floatLink); + trace("Reg float"); + } + } + + case Vector:{ + if(! vectorsRegistered){ + vectorsRegistered = true; + Uniforms.externalVec3Links.push(vec3Link); + } + } + + case Texture:{ + if(! texturesRegistered){ + texturesRegistered = true; + Uniforms.externalTextureLinks.push(textureLink); + } + } + } + } + + public function setFloatValue(material: MaterialData, object: Object, link: String, value: Null){ + + var map = floatsMap; + + var matMap = map.get(object); + if (matMap == null) { + matMap = new Map(); + map.set(object, matMap); + } + + var entry = matMap.get(material); + if (entry == null) { + entry = new Map(); + matMap.set(material, entry); + } + + entry.set(link, value); // parameter name, value + } + + public function setVec3Value(material: MaterialData, object: Object, link: String, value: Vec4){ + + var map = vectorsMap; + + var matMap = map.get(object); + if (matMap == null) { + matMap = new Map(); + map.set(object, matMap); + } + + var entry = matMap.get(material); + if (entry == null) { + entry = new Map(); + matMap.set(material, entry); + } + + entry.set(link, value); // parameter name, value + } + + public function setTextureValue(material: MaterialData, object: Object, link: String, value: kha.Image){ + + var map = texturesMap; + + var matMap = map.get(object); + if (matMap == null) { + matMap = new Map(); + map.set(object, matMap); + } + + var entry = matMap.get(material); + if (entry == null) { + entry = new Map(); + matMap.set(material, entry); + } + + entry.set(link, value); // parameter name, value + } + + static function floatLink(object: Object, mat: MaterialData, link: String): Null { + if(object == null) return null; + if (mat == null) return null; + + if(! floatsMap.exists(object)){ + object = Scene.active.root; + } + + var material = floatsMap.get(object); + if (material == null) return null; + + var entry = material.get(mat); + if (entry == null) return null; + + return entry.get(link); + } + + static function vec3Link(object: Object, mat: MaterialData, link: String): iron.math.Vec4 { + if(object == null) return null; + if (mat == null) return null; + + if(! vectorsMap.exists(object)){ + object = Scene.active.root; + } + + var material = vectorsMap.get(object); + if (material == null) return null; + + var entry = material.get(mat); + if (entry == null) return null; + + return entry.get(link); + } + + static function textureLink(object: Object, mat: MaterialData, link: String): kha.Image { + if(object == null) return null; + if (mat == null) return null; + + if(! texturesMap.exists(object)){ + object = Scene.active.root; + } + + var material = texturesMap.get(object); + if (material == null) return null; + + var entry = material.get(mat); + if (entry == null) return null; + + return entry.get(link); + } +} + +@:enum abstract UniformType(Int) from Int to Int { + var Float = 0; + var Vector = 1; + var Texture = 2; +} \ No newline at end of file From 39bd32f9d3c55f380299a1b0d69c0af61282aaff Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Wed, 9 Jun 2021 20:01:29 +0200 Subject: [PATCH 167/264] get image from node --- Sources/armory/logicnode/SetMaterialImageParamNode.hx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/armory/logicnode/SetMaterialImageParamNode.hx b/Sources/armory/logicnode/SetMaterialImageParamNode.hx index eea685a5..dcea7d31 100644 --- a/Sources/armory/logicnode/SetMaterialImageParamNode.hx +++ b/Sources/armory/logicnode/SetMaterialImageParamNode.hx @@ -28,7 +28,12 @@ class SetMaterialImageParamNode extends LogicNode { object = Scene.active.root; } - manager.setTextureValue(mat, object, inputs[3].get(), inputs[4].get()); + var img = inputs[4].get(); + if(img == null) return; + iron.data.Data.getImage(img, function(image: kha.Image) { + manager.setTextureValue(mat, object, inputs[3].get(), image); + }); + runOutput(0); } } From ea4f88aca826a2952d870b22a0901a211a7989ae Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Wed, 9 Jun 2021 20:01:44 +0200 Subject: [PATCH 168/264] remove debug trace --- Sources/armory/trait/internal/UniformsManager.hx | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/armory/trait/internal/UniformsManager.hx b/Sources/armory/trait/internal/UniformsManager.hx index e5686f97..970ba840 100644 --- a/Sources/armory/trait/internal/UniformsManager.hx +++ b/Sources/armory/trait/internal/UniformsManager.hx @@ -25,7 +25,6 @@ class UniformsManager{ if(! floatsRegistered){ floatsRegistered = true; Uniforms.externalFloatLinks.push(floatLink); - trace("Reg float"); } } From 39922bc0f3f8f1ec497b0596e4aedba53747fe84 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Mon, 14 Jun 2021 13:31:00 +0200 Subject: [PATCH 169/264] move uniforms manager to iron --- .../armory/trait/internal/UniformsManager.hx | 160 ------------------ 1 file changed, 160 deletions(-) delete mode 100644 Sources/armory/trait/internal/UniformsManager.hx diff --git a/Sources/armory/trait/internal/UniformsManager.hx b/Sources/armory/trait/internal/UniformsManager.hx deleted file mode 100644 index 970ba840..00000000 --- a/Sources/armory/trait/internal/UniformsManager.hx +++ /dev/null @@ -1,160 +0,0 @@ -package armory.trait.internal; - -import haxe.ds.Map; -import iron.math.Vec4; -import iron.data.MaterialData; -import iron.Scene; -import iron.object.Object; -import iron.object.Uniforms; - - -class UniformsManager{ - - static var floatsRegistered = false; - static var floatsMap = new Map>>>(); - - static var vectorsRegistered = false; - static var vectorsMap = new Map>>(); - - static var texturesRegistered = false; - static var texturesMap = new Map>>(); - - public function new(type: UniformType){ - switch (type){ - case Float:{ - if(! floatsRegistered){ - floatsRegistered = true; - Uniforms.externalFloatLinks.push(floatLink); - } - } - - case Vector:{ - if(! vectorsRegistered){ - vectorsRegistered = true; - Uniforms.externalVec3Links.push(vec3Link); - } - } - - case Texture:{ - if(! texturesRegistered){ - texturesRegistered = true; - Uniforms.externalTextureLinks.push(textureLink); - } - } - } - } - - public function setFloatValue(material: MaterialData, object: Object, link: String, value: Null){ - - var map = floatsMap; - - var matMap = map.get(object); - if (matMap == null) { - matMap = new Map(); - map.set(object, matMap); - } - - var entry = matMap.get(material); - if (entry == null) { - entry = new Map(); - matMap.set(material, entry); - } - - entry.set(link, value); // parameter name, value - } - - public function setVec3Value(material: MaterialData, object: Object, link: String, value: Vec4){ - - var map = vectorsMap; - - var matMap = map.get(object); - if (matMap == null) { - matMap = new Map(); - map.set(object, matMap); - } - - var entry = matMap.get(material); - if (entry == null) { - entry = new Map(); - matMap.set(material, entry); - } - - entry.set(link, value); // parameter name, value - } - - public function setTextureValue(material: MaterialData, object: Object, link: String, value: kha.Image){ - - var map = texturesMap; - - var matMap = map.get(object); - if (matMap == null) { - matMap = new Map(); - map.set(object, matMap); - } - - var entry = matMap.get(material); - if (entry == null) { - entry = new Map(); - matMap.set(material, entry); - } - - entry.set(link, value); // parameter name, value - } - - static function floatLink(object: Object, mat: MaterialData, link: String): Null { - if(object == null) return null; - if (mat == null) return null; - - if(! floatsMap.exists(object)){ - object = Scene.active.root; - } - - var material = floatsMap.get(object); - if (material == null) return null; - - var entry = material.get(mat); - if (entry == null) return null; - - return entry.get(link); - } - - static function vec3Link(object: Object, mat: MaterialData, link: String): iron.math.Vec4 { - if(object == null) return null; - if (mat == null) return null; - - if(! vectorsMap.exists(object)){ - object = Scene.active.root; - } - - var material = vectorsMap.get(object); - if (material == null) return null; - - var entry = material.get(mat); - if (entry == null) return null; - - return entry.get(link); - } - - static function textureLink(object: Object, mat: MaterialData, link: String): kha.Image { - if(object == null) return null; - if (mat == null) return null; - - if(! texturesMap.exists(object)){ - object = Scene.active.root; - } - - var material = texturesMap.get(object); - if (material == null) return null; - - var entry = material.get(mat); - if (entry == null) return null; - - return entry.get(link); - } -} - -@:enum abstract UniformType(Int) from Int to Int { - var Float = 0; - var Vector = 1; - var Texture = 2; -} \ No newline at end of file From 15da6ccf58395658258f1c73da811a6f87782f49 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Mon, 14 Jun 2021 13:32:00 +0200 Subject: [PATCH 170/264] Upgrade nodes to include object. Add documnetation --- .../material/LN_set_material_image_param.py | 29 ++++++++++++------- .../material/LN_set_material_rgb_param.py | 29 ++++++++++++------- .../material/LN_set_material_value_param.py | 28 +++++++++++------- 3 files changed, 54 insertions(+), 32 deletions(-) diff --git a/blender/arm/logicnode/material/LN_set_material_image_param.py b/blender/arm/logicnode/material/LN_set_material_image_param.py index d442dc9a..e50482c6 100644 --- a/blender/arm/logicnode/material/LN_set_material_image_param.py +++ b/blender/arm/logicnode/material/LN_set_material_image_param.py @@ -1,36 +1,43 @@ from arm.logicnode.arm_nodes import * class SetMaterialImageParamNode(ArmLogicTreeNode): - """Set a image or texture value material parameter to the specified object. If `per object` is disabled, value will be set to all objects with this material""" + """Set an image value material parameter to the specified object. + + @seeNode Get Scene Root + + @input Object: Object whose material parameter should change. Use `Get Scene Root` node to set paramter globally. + + @input Per Object: + - `Enabled`: Set material parameter specific to this object. Global parameter will be ignored. + - `Disabled`: Set parameter globally, including this object. + + @input Material: Material whose parameter to be set. + + @input Node: Name of the parameter. + + @input Image: Name of the image. + """ bl_idname = 'LNSetMaterialImageParamNode' bl_label = 'Set Material Image Param' arm_section = 'params' arm_version = 2 - property0: BoolProperty( - name="Per Object", - description="Set property per object", - default=False - ) - def init(self, context): super(SetMaterialImageParamNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') + self.add_input('NodeSocketBool', 'Per Object') self.add_input('NodeSocketShader', 'Material') self.add_input('NodeSocketString', 'Node') self.add_input('NodeSocketString', 'Image') self.add_output('ArmNodeSocketAction', 'Out') - def draw_buttons(self, context, layout): - layout.prop(self, 'property0') - def get_replacement_node(self, node_tree: bpy.types.NodeTree): if self.arm_version not in (0, 1): raise LookupError() return NodeReplacement( 'LNSetMaterialImageParamNode', self.arm_version, 'LNSetMaterialImageParamNode', 2, - in_socket_mapping={0:0, 1:2, 2:3, 3:4}, out_socket_mapping={0:0} + in_socket_mapping={0:0, 1:3, 2:4, 3:5}, out_socket_mapping={0:0} ) diff --git a/blender/arm/logicnode/material/LN_set_material_rgb_param.py b/blender/arm/logicnode/material/LN_set_material_rgb_param.py index 52ef94b9..67d52f5d 100644 --- a/blender/arm/logicnode/material/LN_set_material_rgb_param.py +++ b/blender/arm/logicnode/material/LN_set_material_rgb_param.py @@ -1,36 +1,43 @@ from arm.logicnode.arm_nodes import * class SetMaterialRgbParamNode(ArmLogicTreeNode): - """Set a color or vector value material parameter to the specified object. If `per object` is disabled, value will be set to all objects with this material""" + """Set a color or vector value material parameter to the specified object. + + @seeNode Get Scene Root + + @input Object: Object whose material parameter should change. Use `Get Scene Root` node to set paramter globally. + + @input Per Object: + - `Enabled`: Set material parameter specific to this object. Global parameter will be ignored. + - `Disabled`: Set parameter globally, including this object. + + @input Material: Material whose parameter to be set. + + @input Node: Name of the parameter. + + @input Color: Color or vector input. + """ bl_idname = 'LNSetMaterialRgbParamNode' bl_label = 'Set Material RGB Param' arm_section = 'params' arm_version = 2 - property0: BoolProperty( - name="Per Object", - description="Set property per object", - default=False - ) - def init(self, context): super(SetMaterialRgbParamNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') + self.add_input('NodeSocketBool', 'Per Object') self.add_input('NodeSocketShader', 'Material') self.add_input('NodeSocketString', 'Node') self.add_input('NodeSocketColor', 'Color') self.add_output('ArmNodeSocketAction', 'Out') - def draw_buttons(self, context, layout): - layout.prop(self, 'property0') - def get_replacement_node(self, node_tree: bpy.types.NodeTree): if self.arm_version not in (0, 1): raise LookupError() return NodeReplacement( 'LNSetMaterialRgbParamNode', self.arm_version, 'LNSetMaterialRgbParamNode', 2, - in_socket_mapping={0:0, 1:2, 2:3, 3:4}, out_socket_mapping={0:0} + in_socket_mapping={0:0, 1:3, 2:4, 3:5}, out_socket_mapping={0:0} ) diff --git a/blender/arm/logicnode/material/LN_set_material_value_param.py b/blender/arm/logicnode/material/LN_set_material_value_param.py index 5e99f14a..d1218b05 100644 --- a/blender/arm/logicnode/material/LN_set_material_value_param.py +++ b/blender/arm/logicnode/material/LN_set_material_value_param.py @@ -1,30 +1,38 @@ from arm.logicnode.arm_nodes import * class SetMaterialValueParamNode(ArmLogicTreeNode): - """Set a float value material parameter to the specified object. If `per object` is disabled, value will be set to all objects with this material""" + """Set a float value material parameter to the specified object. + + @seeNode Get Scene Root + + @input Object: Object whose material parameter should change. Use `Get Scene Root` node to set paramter globally. + + @input Per Object: + - `Enabled`: Set material parameter specific to this object. Global parameter will be ignored. + - `Disabled`: Set parameter globally, including this object. + + @input Material: Material whose parameter to be set. + + @input Node: Name of the parameter. + + @input Float: float value. + """ bl_idname = 'LNSetMaterialValueParamNode' bl_label = 'Set Material Value Param' arm_section = 'params' arm_version = 2 - property0: BoolProperty( - name="Per Object", - description="Set property per object", - default=False - ) - def init(self, context): super(SetMaterialValueParamNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') + self.add_input('NodeSocketBool', 'Per Object') self.add_input('NodeSocketShader', 'Material') self.add_input('NodeSocketString', 'Node') self.add_input('NodeSocketFloat', 'Float') self.add_output('ArmNodeSocketAction', 'Out') - def draw_buttons(self, context, layout): - layout.prop(self, 'property0') def get_replacement_node(self, node_tree: bpy.types.NodeTree): if self.arm_version not in (0, 1): @@ -32,5 +40,5 @@ class SetMaterialValueParamNode(ArmLogicTreeNode): return NodeReplacement( 'LNSetMaterialValueParamNode', self.arm_version, 'LNSetMaterialValueParamNode', 2, - in_socket_mapping={0:0, 1:2, 2:3, 3:4}, out_socket_mapping={0:0} + in_socket_mapping={0:0, 1:3, 2:4, 3:5}, out_socket_mapping={0:0} ) From c44e2cf555e303a704e428c0a90f70a9b5b43899 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Mon, 14 Jun 2021 13:33:35 +0200 Subject: [PATCH 171/264] Pass default vector value when adding uniform --- blender/arm/material/cycles_nodes/nodes_input.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/blender/arm/material/cycles_nodes/nodes_input.py b/blender/arm/material/cycles_nodes/nodes_input.py index 69072bcd..f4b99f63 100644 --- a/blender/arm/material/cycles_nodes/nodes_input.py +++ b/blender/arm/material/cycles_nodes/nodes_input.py @@ -92,7 +92,13 @@ def parse_attribute(node: bpy.types.ShaderNodeAttribute, out_socket: bpy.types.N def parse_rgb(node: bpy.types.ShaderNodeRGB, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: if node.arm_material_param: nn = 'param_' + c.node_name(node.name) - state.curshader.add_uniform(f'vec3 {nn}', link=f'{node.name}') + v = out_socket.default_value + value = [] + value.append(float(v[0])) + value.append(float(v[1])) + value.append(float(v[2])) + is_arm_mat_param = True + state.curshader.add_uniform(f'vec3 {nn}', link=f'{node.name}', default_value = value, is_arm_mat_param = is_arm_mat_param) return nn else: return c.to_vec3(out_socket.default_value) From fa44147ee6f031d3fab3a3ed496a454c2b69dd2f Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Mon, 14 Jun 2021 13:34:30 +0200 Subject: [PATCH 172/264] Pass default float value when adding uniform --- blender/arm/material/cycles_nodes/nodes_input.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/blender/arm/material/cycles_nodes/nodes_input.py b/blender/arm/material/cycles_nodes/nodes_input.py index f4b99f63..335e56f6 100644 --- a/blender/arm/material/cycles_nodes/nodes_input.py +++ b/blender/arm/material/cycles_nodes/nodes_input.py @@ -357,7 +357,9 @@ def parse_lightpath(node: bpy.types.ShaderNodeLightPath, out_socket: bpy.types.N def parse_value(node: bpy.types.ShaderNodeValue, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: if node.arm_material_param: nn = 'param_' + c.node_name(node.name) - state.curshader.add_uniform('float {0}'.format(nn), link='{0}'.format(node.name)) + value = c.to_vec1(node.outputs[0].default_value) + is_arm_mat_param = True + state.curshader.add_uniform('float {0}'.format(nn), link='{0}'.format(node.name), default_value=value, is_arm_mat_param=is_arm_mat_param) return nn else: return c.to_vec1(node.outputs[0].default_value) From 340f7e8af4a261ad72bc78b5e69d30fa919b1c8a Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Mon, 14 Jun 2021 13:35:08 +0200 Subject: [PATCH 173/264] Pass default image file name when adding uniform --- blender/arm/material/cycles_nodes/nodes_texture.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/blender/arm/material/cycles_nodes/nodes_texture.py b/blender/arm/material/cycles_nodes/nodes_texture.py index 516d555b..9952a0cb 100644 --- a/blender/arm/material/cycles_nodes/nodes_texture.py +++ b/blender/arm/material/cycles_nodes/nodes_texture.py @@ -114,15 +114,22 @@ def parse_tex_image(node: bpy.types.ShaderNodeTexImage, out_socket: bpy.types.No tex_name = c.node_name(node.name) tex = c.make_texture(node, tex_name) - tex_link = node.name if node.arm_material_param else None + tex_link = None + tex_default_file = None + is_arm_mat_param = None + if node.arm_material_param: + tex_link = node.name + is_arm_mat_param = True + if tex['file'] is not None: + tex_default_file = tex['file'] if tex is not None: state.curshader.write_textures += 1 if use_color_out: to_linear = node.image is not None and node.image.colorspace_settings.name == 'sRGB' - res = f'{c.texture_store(node, tex, tex_name, to_linear, tex_link=tex_link)}.rgb' + res = f'{c.texture_store(node, tex, tex_name, to_linear, tex_link=tex_link, default_value=tex_default_file, is_arm_mat_param=is_arm_mat_param)}.rgb' else: - res = f'{c.texture_store(node, tex, tex_name, tex_link=tex_link)}.a' + res = f'{c.texture_store(node, tex, tex_name, tex_link=tex_link, default_value=tex_default_file, is_arm_mat_param=is_arm_mat_param)}.a' state.curshader.write_textures -= 1 return res From 975ec76f38533bb380b9bf0a0f3cb981ad7c6d4a Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Mon, 14 Jun 2021 13:35:49 +0200 Subject: [PATCH 174/264] Set default image file --- blender/arm/material/cycles.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blender/arm/material/cycles.py b/blender/arm/material/cycles.py index 20dbd83e..625c24c2 100644 --- a/blender/arm/material/cycles.py +++ b/blender/arm/material/cycles.py @@ -560,7 +560,7 @@ def to_uniform(inp: bpy.types.NodeSocket): def store_var_name(node: bpy.types.Node): return node_name(node.name) + '_store' -def texture_store(node, tex, tex_name, to_linear=False, tex_link=None): +def texture_store(node, tex, tex_name, to_linear=False, tex_link=None, default_value=None, is_arm_mat_param=None): curshader = state.curshader tex_store = store_var_name(node) @@ -569,7 +569,7 @@ def texture_store(node, tex, tex_name, to_linear=False, tex_link=None): state.parsed.add(tex_store) mat_bind_texture(tex) state.con.add_elem('tex', 'short2norm') - curshader.add_uniform('sampler2D {0}'.format(tex_name), link=tex_link) + curshader.add_uniform('sampler2D {0}'.format(tex_name), link=tex_link, default_value=default_value, is_arm_mat_param=is_arm_mat_param) triplanar = node.projection == 'BOX' if node.inputs[0].is_linked: uv_name = parse_vector_input(node.inputs[0]) From 5a591fa15a2691af1c2e1139fe3429cf812aa22f Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Mon, 14 Jun 2021 13:36:30 +0200 Subject: [PATCH 175/264] set default vector and float parameter --- blender/arm/material/shader.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/blender/arm/material/shader.py b/blender/arm/material/shader.py index be2af682..8e107628 100644 --- a/blender/arm/material/shader.py +++ b/blender/arm/material/shader.py @@ -109,19 +109,27 @@ class ShaderContext: def get(self): return self.data - def add_constant(self, ctype, name, link=None): + def add_constant(self, ctype, name, link=None, default_value=None, is_arm_mat_param=None): for c in self.constants: if c['name'] == name: return - c = { 'name': name, 'type': ctype } - if link != None: + c = { 'name': name, 'type': ctype} + if link is not None: c['link'] = link + if default_value is not None: + if ctype == 'float': + c['float'] = default_value + if ctype == 'vec3': + c['vec3'] = default_value + if is_arm_mat_param is not None: + c['is_arm_parameter'] = 'true' self.constants.append(c) def add_texture_unit(self, name, link=None, is_image=None, addr_u=None, addr_v=None, - filter_min=None, filter_mag=None, mipmap_filter=None): + filter_min=None, filter_mag=None, mipmap_filter=None, + default_value=None, is_arm_mat_param=None): for c in self.tunits: if c['name'] == name: return @@ -141,6 +149,10 @@ class ShaderContext: c['filter_mag'] = filter_mag if mipmap_filter is not None: c['mipmap_filter'] = mipmap_filter + if default_value is not None: + c['default_image_file'] = default_value + if is_arm_mat_param is not None: + c['is_arm_parameter'] = 'true' self.tunits.append(c) @@ -238,7 +250,7 @@ class Shader: def add_uniform(self, s, link=None, included=False, top=False, tex_addr_u=None, tex_addr_v=None, tex_filter_min=None, tex_filter_mag=None, - tex_mipmap_filter=None): + tex_mipmap_filter=None, default_value=None, is_arm_mat_param=None): ar = s.split(' ') # layout(RGBA8) image3D voxels utype = ar[-2] @@ -257,7 +269,8 @@ class Shader: self.context.add_texture_unit( uname, link, is_image, tex_addr_u, tex_addr_v, - tex_filter_min, tex_filter_mag, tex_mipmap_filter) + tex_filter_min, tex_filter_mag, tex_mipmap_filter, + default_value=default_value, is_arm_mat_param=is_arm_mat_param) else: # Prefer vec4[] for d3d to avoid padding if ar[0] == 'float' and '[' in ar[1]: @@ -266,7 +279,7 @@ class Shader: elif ar[0] == 'vec4' and '[' in ar[1]: ar[0] = 'floats' ar[1] = ar[1].split('[', 1)[0] - self.context.add_constant(ar[0], ar[1], link=link) + self.context.add_constant(ar[0], ar[1], link=link, default_value=default_value, is_arm_mat_param=is_arm_mat_param) if top: if not included and s not in self.uniforms_top: self.uniforms_top.append(s) From eab7fcbbe6ad54b6ca826ff0d044b6f9e590840b Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Mon, 14 Jun 2021 13:37:10 +0200 Subject: [PATCH 176/264] update node implementations --- .../logicnode/SetMaterialImageParamNode.hx | 19 +++++++++---------- .../logicnode/SetMaterialRgbParamNode.hx | 18 ++++++++---------- .../logicnode/SetMaterialValueParamNode.hx | 19 +++++++++---------- 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/Sources/armory/logicnode/SetMaterialImageParamNode.hx b/Sources/armory/logicnode/SetMaterialImageParamNode.hx index dcea7d31..aa91932e 100644 --- a/Sources/armory/logicnode/SetMaterialImageParamNode.hx +++ b/Sources/armory/logicnode/SetMaterialImageParamNode.hx @@ -3,35 +3,34 @@ package armory.logicnode; import iron.Scene; import iron.data.MaterialData; import iron.object.Object; -import armory.trait.internal.UniformsManager; +import iron.data.UniformsManager; class SetMaterialImageParamNode extends LogicNode { - - public var property0: Bool; // per object - var manager: UniformsManager; - public function new(tree: LogicTree) { super(tree); - manager = new UniformsManager(UniformType.Texture); } override function run(from: Int) { var object = inputs[1].get(); if(object == null) return; - var mat = inputs[2].get(); + var perObject = inputs[2].get(); + if(perObject == null) perObject = false; + + var mat = inputs[3].get(); if(mat == null) return; - if(! property0){ + if(! perObject){ + UniformsManager.removeObjectFromMap(object, Texture); object = Scene.active.root; } - var img = inputs[4].get(); + var img = inputs[5].get(); if(img == null) return; iron.data.Data.getImage(img, function(image: kha.Image) { - manager.setTextureValue(mat, object, inputs[3].get(), image); + UniformsManager.setTextureValue(mat, object, inputs[4].get(), image); }); runOutput(0); diff --git a/Sources/armory/logicnode/SetMaterialRgbParamNode.hx b/Sources/armory/logicnode/SetMaterialRgbParamNode.hx index 802a758e..575cbb96 100644 --- a/Sources/armory/logicnode/SetMaterialRgbParamNode.hx +++ b/Sources/armory/logicnode/SetMaterialRgbParamNode.hx @@ -4,32 +4,30 @@ import iron.Scene; import iron.math.Vec4; import iron.data.MaterialData; import iron.object.Object; -import armory.trait.internal.UniformsManager; +import iron.data.UniformsManager; class SetMaterialRgbParamNode extends LogicNode { - - public var property0: Bool; // per object - var manager: UniformsManager; - public function new(tree: LogicTree) { super(tree); - - manager = new UniformsManager(UniformType.Vector); } override function run(from: Int) { var object = inputs[1].get(); if(object == null) return; - var mat = inputs[2].get(); + var perObject = inputs[2].get(); + if(perObject == null) perObject = false; + + var mat = inputs[3].get(); if(mat == null) return; - if(! property0){ + if(! perObject){ + UniformsManager.removeObjectFromMap(object, Vector); object = Scene.active.root; } - manager.setVec3Value(mat, object, inputs[3].get(), inputs[4].get()); + UniformsManager.setVec3Value(mat, object, inputs[4].get(), inputs[5].get()); runOutput(0); } } diff --git a/Sources/armory/logicnode/SetMaterialValueParamNode.hx b/Sources/armory/logicnode/SetMaterialValueParamNode.hx index ea8e60c3..0c6da7ab 100644 --- a/Sources/armory/logicnode/SetMaterialValueParamNode.hx +++ b/Sources/armory/logicnode/SetMaterialValueParamNode.hx @@ -3,32 +3,31 @@ package armory.logicnode; import iron.Scene; import iron.data.MaterialData; import iron.object.Object; -import armory.trait.internal.UniformsManager; +import iron.data.UniformsManager; class SetMaterialValueParamNode extends LogicNode { - - public var property0: Bool; // per object - var manager: UniformsManager; - public function new(tree: LogicTree) { super(tree); - - manager = new UniformsManager(UniformType.Float); } override function run(from: Int) { var object = inputs[1].get(); if(object == null) return; - var mat = inputs[2].get(); + var perObject = inputs[2].get(); + if(perObject == null) perObject = false; + + var mat = inputs[3].get(); if(mat == null) return; - if(! property0){ + if(! perObject){ + UniformsManager.removeObjectFromMap(object, Float); object = Scene.active.root; } - manager.setFloatValue(mat, object, inputs[3].get(), inputs[4].get()); + UniformsManager.setFloatValue(mat, object, inputs[4].get(), inputs[5].get()); runOutput(0); } + } From 936f11ed8eb193714395364b9357c10f16717435 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Tue, 15 Jun 2021 16:22:42 +0200 Subject: [PATCH 177/264] Move uniforms manager back to armory --- .../armory/trait/internal/UniformsManager.hx | 267 ++++++++++++++++++ .../material/LN_set_material_image_param.py | 2 +- .../material/LN_set_material_rgb_param.py | 2 +- .../material/LN_set_material_value_param.py | 2 +- 4 files changed, 270 insertions(+), 3 deletions(-) create mode 100644 Sources/armory/trait/internal/UniformsManager.hx diff --git a/Sources/armory/trait/internal/UniformsManager.hx b/Sources/armory/trait/internal/UniformsManager.hx new file mode 100644 index 00000000..b41f8f83 --- /dev/null +++ b/Sources/armory/trait/internal/UniformsManager.hx @@ -0,0 +1,267 @@ +package iron.data; + +import kha.Image; +import iron.math.Vec4; +import iron.data.MaterialData; +import iron.Scene; +import iron.object.Object; +import iron.object.Uniforms; + + +class UniformsManager{ + + static var floatsRegistered = false; + static var floatsMap = new Map>>>(); + + static var vectorsRegistered = false; + static var vectorsMap = new Map>>(); + + static var texturesRegistered = false; + static var texturesMap = new Map>>(); + + // Helper method to register float, vec3 and texture getter functions + static function register(type: UniformType){ + + switch (type){ + case Float:{ + if(! floatsRegistered){ + floatsRegistered = true; + Uniforms.externalFloatLinks.push(floatLink); + } + } + + case Vector:{ + if(! vectorsRegistered){ + vectorsRegistered = true; + Uniforms.externalVec3Links.push(vec3Link); + } + } + + case Texture:{ + if(! texturesRegistered){ + texturesRegistered = true; + Uniforms.externalTextureLinks.push(textureLink); + } + } + } + } + + // Register and map shader uniforms if it is an armory shader parameter + public static function registerShaderUniforms(material: MaterialData) { + + if(! floatsMap.exists(Scene.active.root)) floatsMap.set(Scene.active.root, null); + if(! vectorsMap.exists(Scene.active.root)) vectorsMap.set(Scene.active.root, null); + if(! texturesMap.exists(Scene.active.root)) texturesMap.set(Scene.active.root, null); + + for(context in material.shader.raw.contexts){ // For each context in shader + for (constant in context.constants){ // For each constant in the context + if(constant.is_arm_parameter){ // Chack if armory parameter + + var object = Scene.active.root; // Map default uniforms to scene root + + switch (constant.type){ + case "float":{ + var link = constant.link; + var value:Float = constant.float; + setFloatValue(material, object, link, value); + register(Float); + } + + case "vec3":{ + + var vec = new Vec4(); + vec.x = constant.vec3.get(0); + vec.y = constant.vec3.get(1); + vec.z = constant.vec3.get(2); + + setVec3Value(material, object, constant.link, vec); + register(Vector); + + } + } + } + } + for (texture in context.texture_units){ + if(texture.is_arm_parameter){ // Chack if armory parameter + + var object = Scene.active.root; // Map default texture to scene root + + iron.data.Data.getImage(texture.default_image_file, function(image: kha.Image) { + setTextureValue(material, object, texture.link, image); + + }); + register(Texture); + + } + + } + } + + } + + // Method to set map Object -> Material -> Link -> FLoat + public static function setFloatValue(material: MaterialData, object: Object, link: String, value: Null){ + + if(object == null || material == null || link == null) return; + + var map = floatsMap; + + var matMap = map.get(object); + if (matMap == null) { + matMap = new Map(); + map.set(object, matMap); + } + + var entry = matMap.get(material); + if (entry == null) { + entry = new Map(); + matMap.set(material, entry); + } + + entry.set(link, value); // parameter name, value + } + + // Method to set map Object -> Material -> Link -> Vec3 + public static function setVec3Value(material: MaterialData, object: Object, link: String, value: Vec4){ + + if(object == null || material == null || link == null) return; + + var map = vectorsMap; + + var matMap = map.get(object); + if (matMap == null) { + matMap = new Map(); + map.set(object, matMap); + } + + var entry = matMap.get(material); + if (entry == null) { + entry = new Map(); + matMap.set(material, entry); + } + + entry.set(link, value); // parameter name, value + } + + // Method to set map Object -> Material -> Link -> Texture + public static function setTextureValue(material: MaterialData, object: Object, link: String, value: kha.Image){ + + if(object == null || material == null || link == null) return; + + var map = texturesMap; + + var matMap = map.get(object); + if (matMap == null) { + matMap = new Map(); + map.set(object, matMap); + } + + var entry = matMap.get(material); + if (entry == null) { + entry = new Map(); + matMap.set(material, entry); + } + + entry.set(link, value); // parameter name, value + } + + // Mehtod to get object specific material parameter float value + static function floatLink(object: Object, mat: MaterialData, link: String): Null { + + if(object == null || mat == null) return null; + + if(! floatsMap.exists(object)){ + object = Scene.active.root; + } + + var material = floatsMap.get(object); + if (material == null) return null; + + var entry = material.get(mat); + if (entry == null) return null; + + return entry.get(link); + } + + // Mehtod to get object specific material parameter vec3 value + static function vec3Link(object: Object, mat: MaterialData, link: String): iron.math.Vec4 { + + if(object == null || mat == null) return null; + + if(! vectorsMap.exists(object)){ + object = Scene.active.root; + } + + var material = vectorsMap.get(object); + if (material == null) return null; + + var entry = material.get(mat); + if (entry == null) return null; + + return entry.get(link); + } + + // Mehtod to get object specific material parameter texture value + static function textureLink(object: Object, mat: MaterialData, link: String): kha.Image { + + if(object == null || mat == null) return null; + + if(! texturesMap.exists(object)){ + object = Scene.active.root; + } + + var material = texturesMap.get(object); + if (material == null) return null; + + var entry = material.get(mat); + if (entry == null) return null; + + return entry.get(link); + } + + // Returns complete map of float value material paramets + public static function getFloatsMap():Map>>>{ + + return floatsMap; + } + + // Returns complete map of vec3 value material paramets + public static function getVectorsMap():Map>>{ + + return vectorsMap; + } + + // Returns complete map of texture value material paramets + public static function getTexturesMap():Map>>{ + + return texturesMap; + } + + // Remove all object specific material paramenter keys + public static function removeObjectFromAllMaps(object: Object) { + + floatsMap.remove(object); + vectorsMap.remove(object); + texturesMap.remove(object); + + } + + // Remove object specific material paramenter keys + public static function removeObjectFromMap(object: Object, type: UniformType) { + + switch (type){ + case Float: floatsMap.remove(object); + + case Vector: vectorsMap.remove(object); + + case Texture: texturesMap.remove(object); + } + + } +} + +@:enum abstract UniformType(Int) from Int to Int { + var Float = 0; + var Vector = 1; + var Texture = 2; +} \ No newline at end of file diff --git a/blender/arm/logicnode/material/LN_set_material_image_param.py b/blender/arm/logicnode/material/LN_set_material_image_param.py index e50482c6..1002e94e 100644 --- a/blender/arm/logicnode/material/LN_set_material_image_param.py +++ b/blender/arm/logicnode/material/LN_set_material_image_param.py @@ -5,7 +5,7 @@ class SetMaterialImageParamNode(ArmLogicTreeNode): @seeNode Get Scene Root - @input Object: Object whose material parameter should change. Use `Get Scene Root` node to set paramter globally. + @input Object: Object whose material parameter should change. Use `Get Scene Root` node to set parameter globally. @input Per Object: - `Enabled`: Set material parameter specific to this object. Global parameter will be ignored. diff --git a/blender/arm/logicnode/material/LN_set_material_rgb_param.py b/blender/arm/logicnode/material/LN_set_material_rgb_param.py index 67d52f5d..f99c7ff7 100644 --- a/blender/arm/logicnode/material/LN_set_material_rgb_param.py +++ b/blender/arm/logicnode/material/LN_set_material_rgb_param.py @@ -5,7 +5,7 @@ class SetMaterialRgbParamNode(ArmLogicTreeNode): @seeNode Get Scene Root - @input Object: Object whose material parameter should change. Use `Get Scene Root` node to set paramter globally. + @input Object: Object whose material parameter should change. Use `Get Scene Root` node to set parameter globally. @input Per Object: - `Enabled`: Set material parameter specific to this object. Global parameter will be ignored. diff --git a/blender/arm/logicnode/material/LN_set_material_value_param.py b/blender/arm/logicnode/material/LN_set_material_value_param.py index d1218b05..439f54aa 100644 --- a/blender/arm/logicnode/material/LN_set_material_value_param.py +++ b/blender/arm/logicnode/material/LN_set_material_value_param.py @@ -5,7 +5,7 @@ class SetMaterialValueParamNode(ArmLogicTreeNode): @seeNode Get Scene Root - @input Object: Object whose material parameter should change. Use `Get Scene Root` node to set paramter globally. + @input Object: Object whose material parameter should change. Use `Get Scene Root` node to set parameter globally. @input Per Object: - `Enabled`: Set material parameter specific to this object. Global parameter will be ignored. From 5a2f952f896137c13a28e898d3d8636e11be14b2 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Thu, 24 Jun 2021 21:32:48 +0200 Subject: [PATCH 178/264] Add uniforms manager to mesh objects --- blender/arm/exporter.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/blender/arm/exporter.py b/blender/arm/exporter.py index ed408ae7..5546b834 100755 --- a/blender/arm/exporter.py +++ b/blender/arm/exporter.py @@ -2460,6 +2460,13 @@ Make sure the mesh only has tris/quads.""") else: self.material_to_object_dict[mat] = [bobject] self.material_to_arm_object_dict[mat] = [o] + + # Add UniformsManager trait + if type is NodeType.MESH: + uniformManager = {} + uniformManager['type'] = 'Script' + uniformManager['class_name'] = 'armory.trait.internal.UniformsManager' + o['traits'].append(uniformManager) # Export constraints if len(bobject.constraints) > 0: From ee43724b97e2b3bfdd2ea5cee791520eaa3d7c52 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Thu, 24 Jun 2021 21:33:21 +0200 Subject: [PATCH 179/264] Change imports --- Sources/armory/logicnode/SetMaterialImageParamNode.hx | 2 +- Sources/armory/logicnode/SetMaterialRgbParamNode.hx | 2 +- Sources/armory/logicnode/SetMaterialValueParamNode.hx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/armory/logicnode/SetMaterialImageParamNode.hx b/Sources/armory/logicnode/SetMaterialImageParamNode.hx index aa91932e..6b0c0ed8 100644 --- a/Sources/armory/logicnode/SetMaterialImageParamNode.hx +++ b/Sources/armory/logicnode/SetMaterialImageParamNode.hx @@ -3,7 +3,7 @@ package armory.logicnode; import iron.Scene; import iron.data.MaterialData; import iron.object.Object; -import iron.data.UniformsManager; +import armory.trait.internal.UniformsManager; class SetMaterialImageParamNode extends LogicNode { diff --git a/Sources/armory/logicnode/SetMaterialRgbParamNode.hx b/Sources/armory/logicnode/SetMaterialRgbParamNode.hx index 575cbb96..f0be3552 100644 --- a/Sources/armory/logicnode/SetMaterialRgbParamNode.hx +++ b/Sources/armory/logicnode/SetMaterialRgbParamNode.hx @@ -4,7 +4,7 @@ import iron.Scene; import iron.math.Vec4; import iron.data.MaterialData; import iron.object.Object; -import iron.data.UniformsManager; +import armory.trait.internal.UniformsManager; class SetMaterialRgbParamNode extends LogicNode { diff --git a/Sources/armory/logicnode/SetMaterialValueParamNode.hx b/Sources/armory/logicnode/SetMaterialValueParamNode.hx index 0c6da7ab..8523bb8c 100644 --- a/Sources/armory/logicnode/SetMaterialValueParamNode.hx +++ b/Sources/armory/logicnode/SetMaterialValueParamNode.hx @@ -3,7 +3,7 @@ package armory.logicnode; import iron.Scene; import iron.data.MaterialData; import iron.object.Object; -import iron.data.UniformsManager; +import armory.trait.internal.UniformsManager; class SetMaterialValueParamNode extends LogicNode { From 112c00a649f580b182c3d42e7c687d8f83d413f6 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Thu, 24 Jun 2021 21:33:53 +0200 Subject: [PATCH 180/264] Modify unifroms manager --- .../armory/trait/internal/UniformsManager.hx | 60 ++++++++++++++++++- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/Sources/armory/trait/internal/UniformsManager.hx b/Sources/armory/trait/internal/UniformsManager.hx index b41f8f83..549c6d34 100644 --- a/Sources/armory/trait/internal/UniformsManager.hx +++ b/Sources/armory/trait/internal/UniformsManager.hx @@ -1,5 +1,7 @@ -package iron.data; +package armory.trait.internal; +import iron.object.MeshObject; +import iron.Trait; import kha.Image; import iron.math.Vec4; import iron.data.MaterialData; @@ -8,7 +10,7 @@ import iron.object.Object; import iron.object.Uniforms; -class UniformsManager{ +class UniformsManager extends Trait{ static var floatsRegistered = false; static var floatsMap = new Map>>>(); @@ -19,6 +21,52 @@ class UniformsManager{ static var texturesRegistered = false; static var texturesMap = new Map>>(); + static var sceneRemoveInitalized = false; + + public var unifromExists = false; + + public function new(){ + super(); + + notifyOnInit(init); + + notifyOnRemove(removeObject); + + if(! sceneRemoveInitalized){ + + Scene.active.notifyOnRemove(removeScene); + } + } + + function init() { + var materials = cast(object, MeshObject).materials; + + for (material in materials){ + + var exists = registerShaderUniforms(material); + if(exists) { + unifromExists = true; + } + + } + + if(! unifromExists) { + + this.remove(); + } + } + + static function removeScene(){ + + removeObjectFromAllMaps(Scene.active.root); + } + + function removeObject() { + + removeObjectFromAllMaps(object); + + } + // Helper method to register float, vec3 and texture getter functions static function register(type: UniformType){ @@ -47,7 +95,9 @@ class UniformsManager{ } // Register and map shader uniforms if it is an armory shader parameter - public static function registerShaderUniforms(material: MaterialData) { + public static function registerShaderUniforms(material: MaterialData) : Bool { + + var unifromExist = false; if(! floatsMap.exists(Scene.active.root)) floatsMap.set(Scene.active.root, null); if(! vectorsMap.exists(Scene.active.root)) vectorsMap.set(Scene.active.root, null); @@ -57,6 +107,7 @@ class UniformsManager{ for (constant in context.constants){ // For each constant in the context if(constant.is_arm_parameter){ // Chack if armory parameter + unifromExist = true; var object = Scene.active.root; // Map default uniforms to scene root switch (constant.type){ @@ -84,6 +135,7 @@ class UniformsManager{ for (texture in context.texture_units){ if(texture.is_arm_parameter){ // Chack if armory parameter + unifromExist = true; var object = Scene.active.root; // Map default texture to scene root iron.data.Data.getImage(texture.default_image_file, function(image: kha.Image) { @@ -96,6 +148,8 @@ class UniformsManager{ } } + + return unifromExist; } From f2cf3bdeda8013507ca604612cab9f91577310ba Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Thu, 24 Jun 2021 23:12:15 +0200 Subject: [PATCH 181/264] Add new property to set if constraint is relative --- blender/arm/props.py | 1 + blender/arm/props_ui.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/blender/arm/props.py b/blender/arm/props.py index 2b23d334..23e3f170 100755 --- a/blender/arm/props.py +++ b/blender/arm/props.py @@ -286,6 +286,7 @@ def init_properties(): default=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False), size=20, subtype='LAYER') + bpy.types.Object.arm_relative_physics_constraint = BoolProperty(name="Relative Physics Constraint", description="Add physics constraint relative to the parent object or collection when spawned", default=False) bpy.types.Object.arm_animation_enabled = BoolProperty(name="Animation", description="Enable skinning & timeline animation", default=True) bpy.types.Object.arm_tilesheet = StringProperty(name="Tilesheet", description="Set tilesheet animation", default='') bpy.types.Object.arm_tilesheet_action = StringProperty(name="Tilesheet Action", description="Set startup action", default='') diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index 880c7f9a..d69886d1 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -211,8 +211,11 @@ class ARM_PT_PhysicsPropsPanel(bpy.types.Panel): layout.prop(obj, 'arm_rb_trigger') layout.prop(obj, 'arm_rb_ccd') - if obj.soft_body != None: + 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): From ac48fd0bc9ae79b517683abf89273f9cb01368bb Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Thu, 24 Jun 2021 23:12:51 +0200 Subject: [PATCH 182/264] Implement adding of relative physics constraint --- .../bullet/PhysicsConstraintExportHelper.hx | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Sources/armory/trait/physics/bullet/PhysicsConstraintExportHelper.hx b/Sources/armory/trait/physics/bullet/PhysicsConstraintExportHelper.hx index 11a0ac24..bb536bf2 100644 --- a/Sources/armory/trait/physics/bullet/PhysicsConstraintExportHelper.hx +++ b/Sources/armory/trait/physics/bullet/PhysicsConstraintExportHelper.hx @@ -18,8 +18,9 @@ class PhysicsConstraintExportHelper extends iron.Trait { var breakingThreshold: Float; var limits: Array; var constraintAdded: Bool = false; + var relativeConstraint: Bool = false; - public function new(body1: String, body2: String, type: Int, disableCollisions: Bool, breakingThreshold: Float, limits: Array = null) { + public function new(body1: String, body2: String, type: Int, disableCollisions: Bool, breakingThreshold: Float, relatieConstraint: Bool = false, limits: Array = null) { super(); this.body1 = body1; @@ -27,14 +28,26 @@ class PhysicsConstraintExportHelper extends iron.Trait { this.type = type; this.disableCollisions = disableCollisions; this.breakingThreshold = breakingThreshold; + this.relativeConstraint = relatieConstraint; this.limits = limits; notifyOnInit(init); notifyOnUpdate(update); } function init() { - var target1 = Scene.active.getChild(body1); - var target2 = Scene.active.getChild(body2); + var target1; + var target2; + + if(relativeConstraint) { + + target1 = object.parent.getChild(body1); + target2 = object.parent.getChild(body2); + } + else { + + target1 = Scene.active.getChild(body1); + target2 = Scene.active.getChild(body2); + } object.addTrait(new PhysicsConstraint(target1, target2, type, disableCollisions, breakingThreshold, limits)); constraintAdded = true; } From 31a2c9c4d3e8b059a268fcc5f2d0384854bb0497 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Thu, 24 Jun 2021 23:13:29 +0200 Subject: [PATCH 183/264] Modify export of physics constraint --- blender/arm/exporter.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/blender/arm/exporter.py b/blender/arm/exporter.py index ed408ae7..1f9b0213 100755 --- a/blender/arm/exporter.py +++ b/blender/arm/exporter.py @@ -2440,7 +2440,7 @@ Make sure the mesh only has tris/quads.""") # Rigid body constraint rbc = bobject.rigid_body_constraint if rbc is not None and rbc.enabled: - self.add_rigidbody_constraint(o, rbc) + self.add_rigidbody_constraint(o, bobject, rbc) # Camera traits if type is NodeType.CAMERA: @@ -2747,7 +2747,7 @@ Make sure the mesh only has tris/quads.""") o['traits'].append(out_trait) @staticmethod - def add_rigidbody_constraint(o, rbc): + def add_rigidbody_constraint(o, bobject, rbc): rb1 = rbc.object1 rb2 = rbc.object2 if rb1 is None or rb2 is None: @@ -2766,7 +2766,8 @@ Make sure the mesh only has tris/quads.""") "'" + rb1.name + "'", "'" + rb2.name + "'", str(rbc.disable_collisions).lower(), - str(breaking_threshold) + str(breaking_threshold), + str(bobject.arm_relative_physics_constraint).lower() ] } if rbc.type == "FIXED": From 441f42383e3c6296ac24c454ba167c54432f846a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sun, 27 Jun 2021 22:35:37 +0200 Subject: [PATCH 184/264] Correctly stop live patching after player terminates --- blender/arm/live_patch.py | 8 +++++++- blender/arm/make.py | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index 83546001..e0aa2e58 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -19,6 +19,7 @@ msgbus_owner = object() def start(): + """Start the live patch session.""" log.debug("Live patch session started") listen(bpy.types.Object, "location", "obj_location") @@ -32,6 +33,12 @@ def start(): listen(light_type, "energy", "light_energy") +def stop(): + """Stop the live patch session.""" + log.debug("Live patch session stopped") + bpy.msgbus.clear_by_owner(msgbus_owner) + + def patch_export(): """Re-export the current scene and update the game accordingly.""" if state.proc_build is not None: @@ -68,7 +75,6 @@ def patch_done(): js = 'iron.Scene.patch();' write_patch(js) state.proc_build = None - bpy.msgbus.clear_by_owner(msgbus_owner) def write_patch(js: str): diff --git a/blender/arm/make.py b/blender/arm/make.py index cb40c320..8dc9afd2 100755 --- a/blender/arm/make.py +++ b/blender/arm/make.py @@ -398,9 +398,11 @@ def build(target, is_play=False, is_publish=False, is_export=False): shutil.copy(fn, arm.utils.build_dir() + dest + os.path.basename(fn)) def play_done(): + """Called if the player was stopped/terminated.""" state.proc_play = None state.redraw_ui = True log.clear() + live_patch.stop() def assets_done(): if state.proc_build == None: From dfad6902afd7cb18ed61c6690a0740187b09cc5d Mon Sep 17 00:00:00 2001 From: Henrique Date: Sun, 27 Jun 2021 18:18:04 -0300 Subject: [PATCH 185/264] Add Relative Parent option to location nodes --- Sources/armory/logicnode/GetLocationNode.hx | 16 +++++++++++++++- Sources/armory/logicnode/SetLocationNode.hx | 15 +++++++++++++++ .../logicnode/VectorToObjectOrientationNode.hx | 4 +--- .../logicnode/WorldVectorToLocalSpaceNode.hx | 4 +--- .../transform/LN_get_object_location.py | 10 +++++++++- .../transform/LN_set_object_location.py | 10 +++++++++- .../transform/LN_vector_to_object_orientation.py | 5 ++--- .../transform/LN_world_vector_to_local_space.py | 5 ++--- 8 files changed, 54 insertions(+), 15 deletions(-) diff --git a/Sources/armory/logicnode/GetLocationNode.hx b/Sources/armory/logicnode/GetLocationNode.hx index 961f8280..e5f5bee1 100644 --- a/Sources/armory/logicnode/GetLocationNode.hx +++ b/Sources/armory/logicnode/GetLocationNode.hx @@ -1,6 +1,7 @@ package armory.logicnode; import iron.object.Object; +import iron.math.Vec4; class GetLocationNode extends LogicNode { @@ -10,9 +11,22 @@ class GetLocationNode extends LogicNode { override function get(from: Int): Dynamic { var object: Object = inputs[0].get(); + var relative: Bool = inputs[1].get(); if (object == null) return null; - return object.transform.world.getLoc(); + var loc = object.transform.world.getLoc(); + + if (relative) { + loc.sub(object.parent.transform.world.getLoc()); + + var vec = new Vec4(); + vec.x = loc.dot(object.parent.transform.right()); + vec.y = loc.dot(object.parent.transform.look()); + vec.z = loc.dot(object.parent.transform.up()); + loc.setFrom(vec); + } + + return loc; } } diff --git a/Sources/armory/logicnode/SetLocationNode.hx b/Sources/armory/logicnode/SetLocationNode.hx index 709e457b..382b0264 100644 --- a/Sources/armory/logicnode/SetLocationNode.hx +++ b/Sources/armory/logicnode/SetLocationNode.hx @@ -2,10 +2,13 @@ package armory.logicnode; import iron.object.Object; import iron.math.Vec4; +import iron.math.Quat; import armory.trait.physics.RigidBody; class SetLocationNode extends LogicNode { + var quat = new Quat(); + public function new(tree: LogicTree) { super(tree); } @@ -13,9 +16,21 @@ class SetLocationNode extends LogicNode { override function run(from: Int) { var object: Object = inputs[1].get(); var vec: Vec4 = inputs[2].get(); + var relative: Bool = inputs[3].get(); if (object == null || vec == null) return; + if (!relative) { + vec.sub(object.parent.transform.world.getLoc()); // Remove parent location influence + + // Convert vec to parent local space + var vec1 = new Vec4(); + vec1.x = vec.dot(object.parent.transform.right()); + vec1.y = vec.dot(object.parent.transform.look()); + vec1.z = vec.dot(object.parent.transform.up()); + vec.setFrom(vec1); + } + object.transform.loc.setFrom(vec); object.transform.buildMatrix(); diff --git a/Sources/armory/logicnode/VectorToObjectOrientationNode.hx b/Sources/armory/logicnode/VectorToObjectOrientationNode.hx index 2a72590a..27a4b994 100644 --- a/Sources/armory/logicnode/VectorToObjectOrientationNode.hx +++ b/Sources/armory/logicnode/VectorToObjectOrientationNode.hx @@ -3,8 +3,6 @@ package armory.logicnode; import iron.object.Object; import iron.math.Vec4; -using armory.object.TransformExtension; - class VectorToObjectOrientationNode extends LogicNode { public function new(tree: LogicTree) { @@ -18,7 +16,7 @@ class VectorToObjectOrientationNode extends LogicNode { if (object == null || vec == null) return null; - return object.transform.worldVecToOrientation(vec); + return vec.applyQuat(object.transform.rot); } } diff --git a/Sources/armory/logicnode/WorldVectorToLocalSpaceNode.hx b/Sources/armory/logicnode/WorldVectorToLocalSpaceNode.hx index 493ed615..44e12f15 100644 --- a/Sources/armory/logicnode/WorldVectorToLocalSpaceNode.hx +++ b/Sources/armory/logicnode/WorldVectorToLocalSpaceNode.hx @@ -3,8 +3,6 @@ package armory.logicnode; import iron.math.Vec4; import iron.object.Object; -using armory.object.TransformExtension; - class WorldVectorToLocalSpaceNode extends LogicNode { public function new(tree: LogicTree) { @@ -17,7 +15,7 @@ class WorldVectorToLocalSpaceNode extends LogicNode { if (object == null || worldVec == null) return null; - var localVec: Vec4 = new Vec4(); + var localVec = new Vec4(); localVec.x = worldVec.dot(object.transform.right()); localVec.y = worldVec.dot(object.transform.look()); diff --git a/blender/arm/logicnode/transform/LN_get_object_location.py b/blender/arm/logicnode/transform/LN_get_object_location.py index c708ba9a..62fdb9fb 100644 --- a/blender/arm/logicnode/transform/LN_get_object_location.py +++ b/blender/arm/logicnode/transform/LN_get_object_location.py @@ -1,7 +1,14 @@ from arm.logicnode.arm_nodes import * class GetLocationNode(ArmLogicTreeNode): - """Returns the current location of the given object in world coordinates.""" + """Get the location of the given object in world coordinates. + + @input Parent Relative: If enabled, transforms the world coordinates into object parent local coordinates + + @seeNode Set Object Location + @seeNode World Vector to Local Space + @seeNode Vector to Object Orientation + """ bl_idname = 'LNGetLocationNode' bl_label = 'Get Object Location' arm_section = 'location' @@ -10,5 +17,6 @@ class GetLocationNode(ArmLogicTreeNode): def init(self, context): super(GetLocationNode, self).init(context) self.add_input('ArmNodeSocketObject', 'Object') + self.add_input('NodeSocketBool', 'Parent Relative') self.add_output('NodeSocketVector', 'Location') diff --git a/blender/arm/logicnode/transform/LN_set_object_location.py b/blender/arm/logicnode/transform/LN_set_object_location.py index 48afdffb..02bebaf5 100644 --- a/blender/arm/logicnode/transform/LN_set_object_location.py +++ b/blender/arm/logicnode/transform/LN_set_object_location.py @@ -1,7 +1,14 @@ from arm.logicnode.arm_nodes import * class SetLocationNode(ArmLogicTreeNode): - """Sets the location of the given object.""" + """Set the location of the given object in world coordinates. + + @input Parent Relative: If enabled, transforms the world coordinates into object parent local coordinates + + @seeNode Get Object Location + @seeNode World Vector to Local Space + @seeNode Vector to Object Orientation + """ bl_idname = 'LNSetLocationNode' bl_label = 'Set Object Location' arm_section = 'location' @@ -12,5 +19,6 @@ class SetLocationNode(ArmLogicTreeNode): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('NodeSocketVector', 'Location') + self.add_input('NodeSocketBool', 'Parent Relative', default_value=True) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/transform/LN_vector_to_object_orientation.py b/blender/arm/logicnode/transform/LN_vector_to_object_orientation.py index e1402b6a..7c4bbcb0 100644 --- a/blender/arm/logicnode/transform/LN_vector_to_object_orientation.py +++ b/blender/arm/logicnode/transform/LN_vector_to_object_orientation.py @@ -1,10 +1,9 @@ from arm.logicnode.arm_nodes import * class VectorToObjectOrientationNode(ArmLogicTreeNode): - """Converts the given world vector to a vector oriented by the given object. - The object scale is taken in count. + """Transform world coordinates into object oriented coordinates (in other words: apply object rotation to it). - @seeNode World Vector To Object Space + @seeNode World Vector to Object Space @seeNode Get World Orientation @seeNode Vector From Transform """ diff --git a/blender/arm/logicnode/transform/LN_world_vector_to_local_space.py b/blender/arm/logicnode/transform/LN_world_vector_to_local_space.py index 61497f81..7d011fad 100644 --- a/blender/arm/logicnode/transform/LN_world_vector_to_local_space.py +++ b/blender/arm/logicnode/transform/LN_world_vector_to_local_space.py @@ -1,10 +1,9 @@ from arm.logicnode.arm_nodes import * class WorldVectorToLocalSpaceNode(ArmLogicTreeNode): - """Converts the given world vector to a object space vector. - The object scale is taken in count. + """Transform world coordinates into object local coordinates. - @seeNode Vector To Object Orientation + @seeNode Vector to Object Orientation @seeNode Get World Orientation @seeNode Vector From Transform """ From f940e8566e56bb17bfbd8d38dcc8d329cff29bf8 Mon Sep 17 00:00:00 2001 From: Henrique Date: Sun, 27 Jun 2021 18:21:59 -0300 Subject: [PATCH 186/264] Remove unused quat --- Sources/armory/logicnode/GetLocationNode.hx | 5 +++-- Sources/armory/logicnode/SetLocationNode.hx | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/armory/logicnode/GetLocationNode.hx b/Sources/armory/logicnode/GetLocationNode.hx index e5f5bee1..216647e7 100644 --- a/Sources/armory/logicnode/GetLocationNode.hx +++ b/Sources/armory/logicnode/GetLocationNode.hx @@ -18,8 +18,9 @@ class GetLocationNode extends LogicNode { var loc = object.transform.world.getLoc(); if (relative) { - loc.sub(object.parent.transform.world.getLoc()); - + loc.sub(object.parent.transform.world.getLoc()); // Add parent location influence + + // Convert vec to parent local space var vec = new Vec4(); vec.x = loc.dot(object.parent.transform.right()); vec.y = loc.dot(object.parent.transform.look()); diff --git a/Sources/armory/logicnode/SetLocationNode.hx b/Sources/armory/logicnode/SetLocationNode.hx index 382b0264..3c312890 100644 --- a/Sources/armory/logicnode/SetLocationNode.hx +++ b/Sources/armory/logicnode/SetLocationNode.hx @@ -2,7 +2,6 @@ package armory.logicnode; import iron.object.Object; import iron.math.Vec4; -import iron.math.Quat; import armory.trait.physics.RigidBody; class SetLocationNode extends LogicNode { From 13c1e0508d0c2a52d7e1ea3b99583546cfe78628 Mon Sep 17 00:00:00 2001 From: Henrique Date: Sun, 27 Jun 2021 18:26:13 -0300 Subject: [PATCH 187/264] Fix for null parent --- Sources/armory/logicnode/GetLocationNode.hx | 2 +- Sources/armory/logicnode/SetLocationNode.hx | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Sources/armory/logicnode/GetLocationNode.hx b/Sources/armory/logicnode/GetLocationNode.hx index 216647e7..df9c3360 100644 --- a/Sources/armory/logicnode/GetLocationNode.hx +++ b/Sources/armory/logicnode/GetLocationNode.hx @@ -17,7 +17,7 @@ class GetLocationNode extends LogicNode { var loc = object.transform.world.getLoc(); - if (relative) { + if (relative && object.parent != null) { loc.sub(object.parent.transform.world.getLoc()); // Add parent location influence // Convert vec to parent local space diff --git a/Sources/armory/logicnode/SetLocationNode.hx b/Sources/armory/logicnode/SetLocationNode.hx index 3c312890..91e3f5a5 100644 --- a/Sources/armory/logicnode/SetLocationNode.hx +++ b/Sources/armory/logicnode/SetLocationNode.hx @@ -6,8 +6,6 @@ import armory.trait.physics.RigidBody; class SetLocationNode extends LogicNode { - var quat = new Quat(); - public function new(tree: LogicTree) { super(tree); } @@ -19,7 +17,7 @@ class SetLocationNode extends LogicNode { if (object == null || vec == null) return; - if (!relative) { + if (!relative && object.parent != null) { vec.sub(object.parent.transform.world.getLoc()); // Remove parent location influence // Convert vec to parent local space From 34b816b4d950c79f8f3ce5cea7de346d7d6153bf Mon Sep 17 00:00:00 2001 From: Henrique Date: Sun, 27 Jun 2021 18:27:16 -0300 Subject: [PATCH 188/264] Remove default value --- blender/arm/logicnode/transform/LN_set_object_location.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/logicnode/transform/LN_set_object_location.py b/blender/arm/logicnode/transform/LN_set_object_location.py index 02bebaf5..2f2d02c6 100644 --- a/blender/arm/logicnode/transform/LN_set_object_location.py +++ b/blender/arm/logicnode/transform/LN_set_object_location.py @@ -19,6 +19,6 @@ class SetLocationNode(ArmLogicTreeNode): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('NodeSocketVector', 'Location') - self.add_input('NodeSocketBool', 'Parent Relative', default_value=True) + self.add_input('NodeSocketBool', 'Parent Relative') self.add_output('ArmNodeSocketAction', 'Out') From 3d49edee7164a475884822e394bc68bf6c8210de Mon Sep 17 00:00:00 2001 From: Henrique Date: Sun, 27 Jun 2021 19:13:00 -0300 Subject: [PATCH 189/264] Add Subtract option to World Vector to Local Space node --- Sources/armory/logicnode/GetLocationNode.hx | 9 ++++----- Sources/armory/logicnode/SetLocationNode.hx | 9 ++++----- Sources/armory/logicnode/WorldVectorToLocalSpaceNode.hx | 5 +++++ .../transform/LN_world_vector_to_local_space.py | 1 + 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Sources/armory/logicnode/GetLocationNode.hx b/Sources/armory/logicnode/GetLocationNode.hx index df9c3360..58ebbbad 100644 --- a/Sources/armory/logicnode/GetLocationNode.hx +++ b/Sources/armory/logicnode/GetLocationNode.hx @@ -21,11 +21,10 @@ class GetLocationNode extends LogicNode { loc.sub(object.parent.transform.world.getLoc()); // Add parent location influence // Convert vec to parent local space - var vec = new Vec4(); - vec.x = loc.dot(object.parent.transform.right()); - vec.y = loc.dot(object.parent.transform.look()); - vec.z = loc.dot(object.parent.transform.up()); - loc.setFrom(vec); + var dotX = vec.dot(object.parent.transform.right()); + var dotY = vec.dot(object.parent.transform.look()); + var dotZ = vec.dot(object.parent.transform.up()); + loc.set(dotX, dotY, dotZ); } return loc; diff --git a/Sources/armory/logicnode/SetLocationNode.hx b/Sources/armory/logicnode/SetLocationNode.hx index 91e3f5a5..35b65cb0 100644 --- a/Sources/armory/logicnode/SetLocationNode.hx +++ b/Sources/armory/logicnode/SetLocationNode.hx @@ -21,11 +21,10 @@ class SetLocationNode extends LogicNode { vec.sub(object.parent.transform.world.getLoc()); // Remove parent location influence // Convert vec to parent local space - var vec1 = new Vec4(); - vec1.x = vec.dot(object.parent.transform.right()); - vec1.y = vec.dot(object.parent.transform.look()); - vec1.z = vec.dot(object.parent.transform.up()); - vec.setFrom(vec1); + var dotX = vec.dot(object.parent.transform.right()); + var dotY = vec.dot(object.parent.transform.look()); + var dotZ = vec.dot(object.parent.transform.up()); + vec.set(dotX, dotY, dotZ); } object.transform.loc.setFrom(vec); diff --git a/Sources/armory/logicnode/WorldVectorToLocalSpaceNode.hx b/Sources/armory/logicnode/WorldVectorToLocalSpaceNode.hx index 44e12f15..985c7de2 100644 --- a/Sources/armory/logicnode/WorldVectorToLocalSpaceNode.hx +++ b/Sources/armory/logicnode/WorldVectorToLocalSpaceNode.hx @@ -12,11 +12,16 @@ class WorldVectorToLocalSpaceNode extends LogicNode { override function get(from: Int): Vec4 { var object: Object = inputs[0].get(); var worldVec: Vec4 = inputs[1].get(); + var sub: Bool = inputs[2].get(); if (object == null || worldVec == null) return null; var localVec = new Vec4(); + if (sub) { + localVec.sub(object.transform.world.getLoc()); + } + localVec.x = worldVec.dot(object.transform.right()); localVec.y = worldVec.dot(object.transform.look()); localVec.z = worldVec.dot(object.transform.up()); diff --git a/blender/arm/logicnode/transform/LN_world_vector_to_local_space.py b/blender/arm/logicnode/transform/LN_world_vector_to_local_space.py index 7d011fad..e1461132 100644 --- a/blender/arm/logicnode/transform/LN_world_vector_to_local_space.py +++ b/blender/arm/logicnode/transform/LN_world_vector_to_local_space.py @@ -16,5 +16,6 @@ class WorldVectorToLocalSpaceNode(ArmLogicTreeNode): super(WorldVectorToLocalSpaceNode, self).init(context) self.add_input('ArmNodeSocketObject', 'Object') self.add_input('NodeSocketVector', 'World') + self.add_input('NodeSocketBool', 'Subtract', default_value=True) self.add_output('NodeSocketVector', 'Local') From 7515a20d93fd5e5fa698f041975327b09a92ba29 Mon Sep 17 00:00:00 2001 From: Henrique Date: Sun, 27 Jun 2021 20:47:55 -0300 Subject: [PATCH 190/264] Keep compatibility --- Sources/armory/logicnode/GetLocationNode.hx | 20 ++++++++++--------- Sources/armory/logicnode/SetLocationNode.hx | 19 ++++++++++-------- .../logicnode/WorldVectorToLocalSpaceNode.hx | 10 +++++++--- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/Sources/armory/logicnode/GetLocationNode.hx b/Sources/armory/logicnode/GetLocationNode.hx index 58ebbbad..0683350c 100644 --- a/Sources/armory/logicnode/GetLocationNode.hx +++ b/Sources/armory/logicnode/GetLocationNode.hx @@ -1,7 +1,6 @@ package armory.logicnode; import iron.object.Object; -import iron.math.Vec4; class GetLocationNode extends LogicNode { @@ -11,20 +10,23 @@ class GetLocationNode extends LogicNode { override function get(from: Int): Dynamic { var object: Object = inputs[0].get(); - var relative: Bool = inputs[1].get(); if (object == null) return null; var loc = object.transform.world.getLoc(); - if (relative && object.parent != null) { - loc.sub(object.parent.transform.world.getLoc()); // Add parent location influence + if (inputs.length > 1) { // Keep compatibility + var relative: Bool = inputs[1].get(); - // Convert vec to parent local space - var dotX = vec.dot(object.parent.transform.right()); - var dotY = vec.dot(object.parent.transform.look()); - var dotZ = vec.dot(object.parent.transform.up()); - loc.set(dotX, dotY, dotZ); + if (relative && object.parent != null) { + loc.sub(object.parent.transform.world.getLoc()); // Add parent location influence + + // Convert vec to parent local space + var dotX = vec.dot(object.parent.transform.right()); + var dotY = vec.dot(object.parent.transform.look()); + var dotZ = vec.dot(object.parent.transform.up()); + loc.set(dotX, dotY, dotZ); + } } return loc; diff --git a/Sources/armory/logicnode/SetLocationNode.hx b/Sources/armory/logicnode/SetLocationNode.hx index 35b65cb0..f578f813 100644 --- a/Sources/armory/logicnode/SetLocationNode.hx +++ b/Sources/armory/logicnode/SetLocationNode.hx @@ -13,18 +13,21 @@ class SetLocationNode extends LogicNode { override function run(from: Int) { var object: Object = inputs[1].get(); var vec: Vec4 = inputs[2].get(); - var relative: Bool = inputs[3].get(); if (object == null || vec == null) return; - if (!relative && object.parent != null) { - vec.sub(object.parent.transform.world.getLoc()); // Remove parent location influence + if (inputs.length > 3) { // Keep compatibility + var relative: Bool = inputs[3].get(); - // Convert vec to parent local space - var dotX = vec.dot(object.parent.transform.right()); - var dotY = vec.dot(object.parent.transform.look()); - var dotZ = vec.dot(object.parent.transform.up()); - vec.set(dotX, dotY, dotZ); + if (!relative && object.parent != null) { + vec.sub(object.parent.transform.world.getLoc()); // Remove parent location influence + + // Convert vec to parent local space + var dotX = vec.dot(object.parent.transform.right()); + var dotY = vec.dot(object.parent.transform.look()); + var dotZ = vec.dot(object.parent.transform.up()); + vec.set(dotX, dotY, dotZ); + } } object.transform.loc.setFrom(vec); diff --git a/Sources/armory/logicnode/WorldVectorToLocalSpaceNode.hx b/Sources/armory/logicnode/WorldVectorToLocalSpaceNode.hx index 985c7de2..e774f75b 100644 --- a/Sources/armory/logicnode/WorldVectorToLocalSpaceNode.hx +++ b/Sources/armory/logicnode/WorldVectorToLocalSpaceNode.hx @@ -12,14 +12,18 @@ class WorldVectorToLocalSpaceNode extends LogicNode { override function get(from: Int): Vec4 { var object: Object = inputs[0].get(); var worldVec: Vec4 = inputs[1].get(); - var sub: Bool = inputs[2].get(); + if (object == null || worldVec == null) return null; var localVec = new Vec4(); - if (sub) { - localVec.sub(object.transform.world.getLoc()); + if (inputs.length > 2) { // Keep compatibility + var sub: Bool = inputs[2].get(); + + if (sub) { + localVec.sub(object.transform.world.getLoc()); + } } localVec.x = worldVec.dot(object.transform.right()); From 202138304adf0d9c12ed7aaaef278572bbe25721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Mon, 28 Jun 2021 12:07:57 +0200 Subject: [PATCH 191/264] Live patch: add support for creating connections between nodes --- Sources/armory/logicnode/LogicNode.hx | 13 +++++--- Sources/armory/logicnode/LogicTree.hx | 13 ++++++++ Sources/armory/trait/internal/LivePatch.hx | 24 +++++++++++-- blender/arm/live_patch.py | 37 ++++++++++++++++---- blender/arm/logicnode/arm_nodes.py | 10 +++++- blender/arm/make_logic.py | 31 +++++++++++------ blender/arm/node_utils.py | 39 +++++++++++++++++++++- 7 files changed, 140 insertions(+), 27 deletions(-) diff --git a/Sources/armory/logicnode/LogicNode.hx b/Sources/armory/logicnode/LogicNode.hx index 9e5e367c..9255a559 100644 --- a/Sources/armory/logicnode/LogicNode.hx +++ b/Sources/armory/logicnode/LogicNode.hx @@ -6,12 +6,15 @@ class LogicNode { var inputs: Array = []; var outputs: Array> = []; - #if arm_debug + #if (arm_debug || arm_patch) public var name = ""; - public function watch(b: Bool) { // Watch in debug console - var nodes = armory.trait.internal.DebugConsole.watchNodes; - b ? nodes.push(this) : nodes.remove(this); - } + + #if (arm_debug) + public function watch(b: Bool) { // Watch in debug console + var nodes = armory.trait.internal.DebugConsole.watchNodes; + b ? nodes.push(this) : nodes.remove(this); + } + #end #end public function new(tree: LogicTree) { diff --git a/Sources/armory/logicnode/LogicTree.hx b/Sources/armory/logicnode/LogicTree.hx index ce2b26e4..22daf13a 100644 --- a/Sources/armory/logicnode/LogicTree.hx +++ b/Sources/armory/logicnode/LogicTree.hx @@ -2,10 +2,23 @@ package armory.logicnode; class LogicTree extends iron.Trait { + #if arm_patch + public static var nodeTrees = new Map(); + + /** + [node name => logic node] for later node replacement for live patching. + **/ + public var nodes: Map; + #end + public var loopBreak = false; // Trigger break from loop nodes public function new() { super(); + + #if arm_patch + nodes = new Map(); + #end } public function add() {} diff --git a/Sources/armory/trait/internal/LivePatch.hx b/Sources/armory/trait/internal/LivePatch.hx index 220d42ba..49c4cd26 100644 --- a/Sources/armory/trait/internal/LivePatch.hx +++ b/Sources/armory/trait/internal/LivePatch.hx @@ -1,8 +1,16 @@ package armory.trait.internal; +import armory.logicnode.LogicNode.LogicNodeInput; +import armory.logicnode.LogicTree; + + +#if arm_patch @:expose("LivePatch") #end +@:access(armory.logicnode.LogicNode) class LivePatch extends iron.Trait { -#if arm_patch +#if !arm_patch + public function new() { super(); } +#else static var patchId = 0; @@ -23,9 +31,19 @@ class LivePatch extends iron.Trait { }); } -#else + public static function patchCreateNodeLink(treeName: String, fromNodeName: String, toNodeName: String, fromIndex: Int, toIndex: Int) { + var tree = LogicTree.nodeTrees[treeName]; + if (tree == null) return; - public function new() { super(); } + var fromNode = tree.nodes[fromNodeName]; + var toNode = tree.nodes[toNodeName]; + if (fromNode == null || toNode == null) return; + // Don't add a connection twice + if (!fromNode.outputs[fromIndex].contains(toNode)) { + fromNode.outputs[fromIndex].push(toNode); + } + toNode.inputs[toIndex] = new LogicNodeInput(fromNode, fromIndex); + } #end } diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index e0aa2e58..1403d58c 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -1,12 +1,14 @@ import os import shutil -from typing import Type +from typing import Any, Type import bpy -import arm.assets as assets +import arm.assets +import arm.node_utils from arm.exporter import ArmoryExporter import arm.log as log +from arm.logicnode.arm_nodes import ArmLogicTreeNode import arm.make as make import arm.make_state as state import arm.utils @@ -44,7 +46,7 @@ def patch_export(): if state.proc_build is not None: return - assets.invalidate_enabled = False + arm.assets.invalidate_enabled = False with arm.utils.WorkingDir(arm.utils.get_fp()): asset_path = arm.utils.get_fp_build() + '/compiled/Assets/' + arm.utils.safestr(bpy.context.scene.name) + '.arm' @@ -66,7 +68,7 @@ def patch_export(): '--noproject' ] - assets.invalidate_enabled = True + arm.assets.invalidate_enabled = True state.proc_build = make.run_proc(cmd, patch_done) @@ -99,7 +101,7 @@ def listen(rna_type: Type[bpy.types.bpy_struct], prop: str, event_id: str): ) -def send_event(event_id: str): +def send_event(event_id: str, opt_data: Any = None): """Send the result of the given event to Krom.""" if hasattr(bpy.context, 'object') and bpy.context.object is not None: obj = bpy.context.object.name @@ -143,6 +145,29 @@ def send_event(event_id: str): else: patch_export() + if event_id == 'ln_insert_link': + node: ArmLogicTreeNode + link: bpy.types.NodeLink + node, link = opt_data + + # This event is called twice for a connection but we only need + # send it once + if node == link.from_node: + node_tree = node.get_tree() + tree_name = arm.node_utils.get_export_tree_name(node_tree) + + # [1:] is used here because make_logic already uses that for + # node names if arm_debug is used + from_node_name = arm.node_utils.get_export_node_name(node)[1:] + to_node_name = arm.node_utils.get_export_node_name(link.to_node)[1:] + + from_index = arm.node_utils.get_socket_index(node.outputs, link.from_socket) + to_index = arm.node_utils.get_socket_index(link.to_node.inputs, link.to_socket) + + js = f'LivePatch.patchCreateNodeLink("{tree_name}", "{from_node_name}", "{to_node_name}", "{from_index}", "{to_index}");' + write_patch(js) + + def on_operator(operator_id: str): """As long as bpy.msgbus doesn't listen to changes made by @@ -151,7 +176,7 @@ def on_operator(operator_id: str): (*) https://developer.blender.org/T72109 """ # Don't re-export the scene for the following operators - if operator_id in ("VIEW3D_OT_select", "OUTLINER_OT_item_activate", "OBJECT_OT_editmode_toggle"): + if operator_id in ("VIEW3D_OT_select", "OUTLINER_OT_item_activate", "OBJECT_OT_editmode_toggle", "NODE_OT_select", "NODE_OT_translate_attach_remove_on_cancel"): return if operator_id == "TRANSFORM_OT_translate": diff --git a/blender/arm/logicnode/arm_nodes.py b/blender/arm/logicnode/arm_nodes.py index e99b8bd2..1a0dad15 100644 --- a/blender/arm/logicnode/arm_nodes.py +++ b/blender/arm/logicnode/arm_nodes.py @@ -7,7 +7,8 @@ import bpy.types from bpy.props import * from nodeitems_utils import NodeItem -# Pass NodeReplacment forward to individual node modules that import arm_nodes +import arm # we cannot import arm.livepatch here or we have a circular import +# Pass NodeReplacement forward to individual node modules that import arm_nodes from arm.logicnode.replacement import NodeReplacement import arm.node_utils @@ -48,6 +49,13 @@ class ArmLogicTreeNode(bpy.types.Node): def on_unregister(cls): pass + def get_tree(self): + return self.id_data + + def insert_link(self, link: bpy.types.NodeLink): + """Called on *both* nodes when a link between two nodes is created.""" + arm.live_patch.send_event('ln_insert_link', (self, link)) + def get_replacement_node(self, node_tree: bpy.types.NodeTree): # needs to be overridden by individual node classes with arm_version>1 """(only called if the node's version is inferior to the node class's version) diff --git a/blender/arm/make_logic.py b/blender/arm/make_logic.py index 6ee7aac8..289bb6b0 100755 --- a/blender/arm/make_logic.py +++ b/blender/arm/make_logic.py @@ -5,6 +5,7 @@ import bpy from arm.exporter import ArmoryExporter import arm.log +import arm.node_utils import arm.utils parsed_nodes = [] @@ -13,14 +14,16 @@ function_nodes = dict() function_node_outputs = dict() group_name = '' -def get_logic_trees(): + +def get_logic_trees() -> list['arm.nodes_logic.ArmLogicTree']: ar = [] for node_group in bpy.data.node_groups: if node_group.bl_idname == 'ArmLogicTreeType': - node_group.use_fake_user = True # Keep fake references for now + node_group.use_fake_user = True # Keep fake references for now ar.append(node_group) return ar + # Generating node sources def build(): os.chdir(arm.utils.get_fp()) @@ -34,7 +37,7 @@ def build(): for tree in trees: build_node_tree(tree) -def build_node_tree(node_group): +def build_node_tree(node_group: 'arm.nodes_logic.ArmLogicTree'): global parsed_nodes global parsed_ids global function_nodes @@ -48,12 +51,8 @@ def build_node_tree(node_group): pack_path = arm.utils.safestr(bpy.data.worlds['Arm'].arm_project_package) path = 'Sources/' + pack_path.replace('.', '/') + '/node/' - group_name = arm.utils.safesrc(node_group.name[0].upper() + node_group.name[1:]) - - if group_name != node_group.name: - arm.log.warn('Logic node tree and generated trait names differ! Node' - f' tree: "{node_group.name}", trait: "{group_name}"') + group_name = arm.node_utils.get_export_tree_name(node_group, do_warn=True) file = path + group_name + '.hx' # Import referenced node group @@ -65,6 +64,8 @@ def build_node_tree(node_group): if node_group.arm_cached and os.path.isfile(file): return + wrd = bpy.data.worlds['Arm'] + with open(file, 'w', encoding="utf-8") as f: f.write('package ' + pack_path + '.node;\n\n') f.write('@:keep class ' + group_name + ' extends armory.logicnode.LogicTree {\n\n') @@ -72,10 +73,12 @@ def build_node_tree(node_group): f.write('\tvar functionOutputNodes:Map;\n\n') f.write('\tpublic function new() {\n') f.write('\t\tsuper();\n') - if bpy.data.worlds['Arm'].arm_debug_console: + if wrd.arm_debug_console: f.write('\t\tname = "' + group_name + '";\n') f.write('\t\tthis.functionNodes = new Map();\n') f.write('\t\tthis.functionOutputNodes = new Map();\n') + if wrd.arm_live_patch: + f.write(f'\t\tarmory.logicnode.LogicTree.nodeTrees["{group_name}"] = this;\n') f.write('\t\tnotifyOnAdd(add);\n') f.write('\t}\n\n') f.write('\toverride public function add() {\n') @@ -116,7 +119,7 @@ def build_node(node: bpy.types.Node, f: TextIO) -> Optional[str]: return None # Get node name - name = '_' + arm.utils.safesrc(node.name) + name = arm.node_utils.get_export_node_name(node) # Link nodes using IDs if node.arm_logic_id != '': @@ -143,11 +146,17 @@ def build_node(node: bpy.types.Node, f: TextIO) -> Optional[str]: # Index function output name by corresponding function name function_node_outputs[node.function_name] = name + wrd = bpy.data.worlds['Arm'] + # Watch in debug console - if node.arm_watch and bpy.data.worlds['Arm'].arm_debug_console: + if node.arm_watch and wrd.arm_debug_console: f.write('\t\t' + name + '.name = "' + name[1:] + '";\n') f.write('\t\t' + name + '.watch(true);\n') + elif wrd.arm_live_patch: + f.write('\t\t' + name + '.name = "' + name[1:] + '";\n') + f.write(f'\t\tthis.nodes["{name[1:]}"] = {name};\n') + # Properties for i in range(0, 10): prop_name = 'property' + str(i) + '_get' diff --git a/blender/arm/node_utils.py b/blender/arm/node_utils.py index e8808f49..5cee6346 100755 --- a/blender/arm/node_utils.py +++ b/blender/arm/node_utils.py @@ -1,8 +1,12 @@ -from typing import Type +from typing import Type, Union import bpy +from bpy.types import NodeSocket, NodeInputs, NodeOutputs from nodeitems_utils import NodeItem +import arm.log +import arm.utils + def find_node_by_link(node_group, to_node, inp): for link in node_group.links: @@ -46,6 +50,39 @@ def get_output_node(node_group, from_node, output_index): return link.to_node +def get_socket_index(sockets: Union[NodeInputs, NodeOutputs], socket: NodeSocket) -> int: + """Find the socket index in the given node input or output + collection, return -1 if not found. + """ + for i in range(0, len(sockets)): + if sockets[i] == socket: + return i + return -1 + + +def get_export_tree_name(tree: bpy.types.NodeTree, do_warn=False) -> str: + """Return the name of the given node tree that's used in the + exported Haxe code. + + If `do_warn` is true, a warning is displayed if the export name + differs from the actual tree name. + """ + export_name = arm.utils.safesrc(tree.name[0].upper() + tree.name[1:]) + + if export_name != tree.name: + arm.log.warn('Logic node tree and generated trait names differ! Node' + f' tree: "{tree.name}", trait: "{export_name}"') + + return export_name + + +def get_export_node_name(node: bpy.types.Node) -> str: + """Return the name of the given node that's used in the exported + Haxe code. + """ + return '_' + arm.utils.safesrc(node.name) + + def nodetype_to_nodeitem(node_type: Type[bpy.types.Node]) -> NodeItem: """Create a NodeItem from a given node class.""" # Internal node types seem to have no bl_idname attribute From c7e1f5d0a9a2d9a543699366e768032a4202205e Mon Sep 17 00:00:00 2001 From: Henrique Date: Mon, 28 Jun 2021 16:12:05 -0300 Subject: [PATCH 192/264] Fix nodes --- Sources/armory/logicnode/GetLocationNode.hx | 19 +++++++--------- Sources/armory/logicnode/SetLocationNode.hx | 22 +++++++++---------- .../logicnode/WorldVectorToLocalSpaceNode.hx | 10 +-------- .../transform/LN_get_object_location.py | 7 +++++- .../transform/LN_set_object_location.py | 7 +++++- .../LN_world_vector_to_local_space.py | 1 - 6 files changed, 31 insertions(+), 35 deletions(-) diff --git a/Sources/armory/logicnode/GetLocationNode.hx b/Sources/armory/logicnode/GetLocationNode.hx index 0683350c..00684473 100644 --- a/Sources/armory/logicnode/GetLocationNode.hx +++ b/Sources/armory/logicnode/GetLocationNode.hx @@ -10,23 +10,20 @@ class GetLocationNode extends LogicNode { override function get(from: Int): Dynamic { var object: Object = inputs[0].get(); + var relative: Bool = inputs[1].get(); if (object == null) return null; var loc = object.transform.world.getLoc(); - if (inputs.length > 1) { // Keep compatibility - var relative: Bool = inputs[1].get(); + if (relative && object.parent != null) { + loc.sub(object.parent.transform.world.getLoc()); // Add parent location influence - if (relative && object.parent != null) { - loc.sub(object.parent.transform.world.getLoc()); // Add parent location influence - - // Convert vec to parent local space - var dotX = vec.dot(object.parent.transform.right()); - var dotY = vec.dot(object.parent.transform.look()); - var dotZ = vec.dot(object.parent.transform.up()); - loc.set(dotX, dotY, dotZ); - } + // Convert loc to parent local space + var dotX = loc.dot(object.parent.transform.right()); + var dotY = loc.dot(object.parent.transform.look()); + var dotZ = loc.dot(object.parent.transform.up()); + loc.set(dotX, dotY, dotZ); } return loc; diff --git a/Sources/armory/logicnode/SetLocationNode.hx b/Sources/armory/logicnode/SetLocationNode.hx index f578f813..4f879586 100644 --- a/Sources/armory/logicnode/SetLocationNode.hx +++ b/Sources/armory/logicnode/SetLocationNode.hx @@ -13,21 +13,19 @@ class SetLocationNode extends LogicNode { override function run(from: Int) { var object: Object = inputs[1].get(); var vec: Vec4 = inputs[2].get(); + var relative: Bool = inputs[3].get(); if (object == null || vec == null) return; - if (inputs.length > 3) { // Keep compatibility - var relative: Bool = inputs[3].get(); + if (!relative && object.parent != null) { + var loc = vec.clone(); + loc.sub(object.parent.transform.world.getLoc()); // Remove parent location influence - if (!relative && object.parent != null) { - vec.sub(object.parent.transform.world.getLoc()); // Remove parent location influence - - // Convert vec to parent local space - var dotX = vec.dot(object.parent.transform.right()); - var dotY = vec.dot(object.parent.transform.look()); - var dotZ = vec.dot(object.parent.transform.up()); - vec.set(dotX, dotY, dotZ); - } + // Convert vec to parent local space + var dotX = loc.dot(object.parent.transform.right()); + var dotY = loc.dot(object.parent.transform.look()); + var dotZ = loc.dot(object.parent.transform.up()); + vec.set(dotX, dotY, dotZ); } object.transform.loc.setFrom(vec); @@ -40,4 +38,4 @@ class SetLocationNode extends LogicNode { runOutput(0); } -} +} \ No newline at end of file diff --git a/Sources/armory/logicnode/WorldVectorToLocalSpaceNode.hx b/Sources/armory/logicnode/WorldVectorToLocalSpaceNode.hx index e774f75b..75f128c1 100644 --- a/Sources/armory/logicnode/WorldVectorToLocalSpaceNode.hx +++ b/Sources/armory/logicnode/WorldVectorToLocalSpaceNode.hx @@ -13,18 +13,10 @@ class WorldVectorToLocalSpaceNode extends LogicNode { var object: Object = inputs[0].get(); var worldVec: Vec4 = inputs[1].get(); - if (object == null || worldVec == null) return null; var localVec = new Vec4(); - - if (inputs.length > 2) { // Keep compatibility - var sub: Bool = inputs[2].get(); - - if (sub) { - localVec.sub(object.transform.world.getLoc()); - } - } + localVec.sub(object.transform.world.getLoc()); localVec.x = worldVec.dot(object.transform.right()); localVec.y = worldVec.dot(object.transform.look()); diff --git a/blender/arm/logicnode/transform/LN_get_object_location.py b/blender/arm/logicnode/transform/LN_get_object_location.py index 62fdb9fb..76458b5b 100644 --- a/blender/arm/logicnode/transform/LN_get_object_location.py +++ b/blender/arm/logicnode/transform/LN_get_object_location.py @@ -12,7 +12,7 @@ class GetLocationNode(ArmLogicTreeNode): bl_idname = 'LNGetLocationNode' bl_label = 'Get Object Location' arm_section = 'location' - arm_version = 1 + arm_version = 2 def init(self, context): super(GetLocationNode, self).init(context) @@ -20,3 +20,8 @@ class GetLocationNode(ArmLogicTreeNode): self.add_input('NodeSocketBool', 'Parent Relative') self.add_output('NodeSocketVector', 'Location') + + def get_replacement_node(self, node_tree: bpy.types.NodeTree): + if self.arm_version not in (0, 1): + raise LookupError() + return NodeReplacement.Identity(self) \ No newline at end of file diff --git a/blender/arm/logicnode/transform/LN_set_object_location.py b/blender/arm/logicnode/transform/LN_set_object_location.py index 2f2d02c6..5a2b9824 100644 --- a/blender/arm/logicnode/transform/LN_set_object_location.py +++ b/blender/arm/logicnode/transform/LN_set_object_location.py @@ -12,7 +12,7 @@ class SetLocationNode(ArmLogicTreeNode): bl_idname = 'LNSetLocationNode' bl_label = 'Set Object Location' arm_section = 'location' - arm_version = 1 + arm_version = 2 def init(self, context): super(SetLocationNode, self).init(context) @@ -22,3 +22,8 @@ class SetLocationNode(ArmLogicTreeNode): self.add_input('NodeSocketBool', 'Parent Relative') self.add_output('ArmNodeSocketAction', 'Out') + + def get_replacement_node(self, node_tree: bpy.types.NodeTree): + if self.arm_version not in (0, 1): + raise LookupError() + return NodeReplacement.Identity(self) \ No newline at end of file diff --git a/blender/arm/logicnode/transform/LN_world_vector_to_local_space.py b/blender/arm/logicnode/transform/LN_world_vector_to_local_space.py index e1461132..7d011fad 100644 --- a/blender/arm/logicnode/transform/LN_world_vector_to_local_space.py +++ b/blender/arm/logicnode/transform/LN_world_vector_to_local_space.py @@ -16,6 +16,5 @@ class WorldVectorToLocalSpaceNode(ArmLogicTreeNode): super(WorldVectorToLocalSpaceNode, self).init(context) self.add_input('ArmNodeSocketObject', 'Object') self.add_input('NodeSocketVector', 'World') - self.add_input('NodeSocketBool', 'Subtract', default_value=True) self.add_output('NodeSocketVector', 'Local') From 7504d5d92b22fa3c7e3f9ffa93501bf2f405561b Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Thu, 1 Jul 2021 15:32:53 +0200 Subject: [PATCH 193/264] Change input type from shader to vector --- blender/arm/logicnode/navmesh/LN_go_to_location.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/logicnode/navmesh/LN_go_to_location.py b/blender/arm/logicnode/navmesh/LN_go_to_location.py index eba3cf97..7f7d6e9d 100644 --- a/blender/arm/logicnode/navmesh/LN_go_to_location.py +++ b/blender/arm/logicnode/navmesh/LN_go_to_location.py @@ -10,7 +10,7 @@ class GoToLocationNode(ArmLogicTreeNode): super(GoToLocationNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketShader', 'Location') + self.add_input('NodeSocketVector', 'Location') self.add_output('ArmNodeSocketAction', 'Out') From 9e493f6e79f71f712c5e312cd5117d3cfde40bd4 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Thu, 1 Jul 2021 15:33:28 +0200 Subject: [PATCH 194/264] do not remove trait at init --- Sources/armory/trait/internal/UniformsManager.hx | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/Sources/armory/trait/internal/UniformsManager.hx b/Sources/armory/trait/internal/UniformsManager.hx index 549c6d34..ed1ba2d5 100644 --- a/Sources/armory/trait/internal/UniformsManager.hx +++ b/Sources/armory/trait/internal/UniformsManager.hx @@ -47,24 +47,17 @@ class UniformsManager extends Trait{ if(exists) { unifromExists = true; } - - } - - if(! unifromExists) { - - this.remove(); } } - static function removeScene(){ + static function removeScene() { removeObjectFromAllMaps(Scene.active.root); } function removeObject() { - removeObjectFromAllMaps(object); - + removeObjectFromAllMaps(object); } // Helper method to register float, vec3 and texture getter functions @@ -310,7 +303,6 @@ class UniformsManager extends Trait{ case Texture: texturesMap.remove(object); } - } } From 4387d774cc2d9036a67fffd47b6828aa81a48c58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 3 Jul 2021 19:45:05 +0200 Subject: [PATCH 195/264] Live patch: add support for node property updates --- Sources/armory/logicnode/AddRigidBodyNode.hx | 39 ++- Sources/armory/logicnode/MathNode.hx | 6 +- Sources/armory/logicnode/MixNode.hx | 6 +- .../armory/logicnode/PhysicsConstraintNode.hx | 35 +-- Sources/armory/logicnode/VectorMixNode.hx | 4 +- Sources/armory/trait/internal/LivePatch.hx | 10 + blender/arm/live_patch.py | 15 +- blender/arm/logicnode/arm_nodes.py | 13 +- blender/arm/logicnode/arm_props.py | 267 ++++++++++++++++++ .../logicnode/canvas/LN_on_canvas_element.py | 9 +- .../arm/logicnode/deprecated/LN_on_gamepad.py | 6 +- .../logicnode/deprecated/LN_on_keyboard.py | 6 +- .../arm/logicnode/deprecated/LN_on_mouse.py | 6 +- .../arm/logicnode/deprecated/LN_on_surface.py | 3 +- .../deprecated/LN_on_virtual_button.py | 5 +- blender/arm/logicnode/event/LN_on_event.py | 2 +- blender/arm/logicnode/event/LN_on_update.py | 3 +- blender/arm/logicnode/input/LN_gamepad.py | 6 +- blender/arm/logicnode/input/LN_keyboard.py | 6 +- blender/arm/logicnode/input/LN_mouse.py | 6 +- .../logicnode/input/LN_set_cursor_state.py | 3 +- blender/arm/logicnode/input/LN_touch.py | 3 +- .../arm/logicnode/input/LN_virtual_button.py | 5 +- blender/arm/logicnode/logic/LN_gate.py | 5 +- blender/arm/logicnode/material/LN_material.py | 2 +- blender/arm/logicnode/math/LN_compare.py | 5 +- blender/arm/logicnode/math/LN_math.py | 11 +- .../arm/logicnode/math/LN_math_expression.py | 4 +- blender/arm/logicnode/math/LN_matrix_math.py | 3 +- blender/arm/logicnode/math/LN_mix.py | 14 +- blender/arm/logicnode/math/LN_mix_vector.py | 14 +- .../arm/logicnode/math/LN_quaternion_math.py | 5 +- .../math/LN_screen_to_world_space.py | 11 +- blender/arm/logicnode/math/LN_vector_math.py | 5 +- .../logicnode/miscellaneous/LN_call_group.py | 2 +- .../LN_set_debug_console_settings.py | 3 +- blender/arm/logicnode/native/LN_expression.py | 2 +- blender/arm/logicnode/native/LN_script.py | 2 +- .../logicnode/object/LN_get_object_child.py | 3 +- blender/arm/logicnode/object/LN_mesh.py | 2 +- .../logicnode/object/LN_set_object_visible.py | 3 +- .../object/LN_spawn_object_by_name.py | 3 +- .../logicnode/physics/LN_Add_rigid_body.py | 14 +- .../physics/LN_add_physics_constraint.py | 3 +- .../arm/logicnode/physics/LN_on_contact.py | 3 +- .../logicnode/physics/LN_on_contact_array.py | 3 +- .../logicnode/physics/LN_on_volume_trigger.py | 3 +- .../physics/LN_physics_constraint.py | 45 ++- .../physics/LN_set_rb_activation_state.py | 3 +- .../logicnode/physics/LN_volume_trigger.py | 3 +- .../LN_colorgrading_set_global_node.py | 3 +- .../LN_colorgrading_set_highlight_node.py | 3 +- .../LN_colorgrading_set_midtone_node.py | 3 +- .../LN_colorgrading_set_shadow_node.py | 3 +- .../renderpath/LN_set_msaa_quality.py | 3 +- .../renderpath/LN_set_post_process_quality.py | 3 +- .../renderpath/LN_set_shader_uniform.py | 3 +- .../renderpath/LN_set_shadows_quality.py | 3 +- .../renderpath/LN_set_ssaa_quality.py | 3 +- blender/arm/logicnode/scene/LN_collection.py | 2 +- blender/arm/logicnode/scene/LN_scene.py | 2 +- .../logicnode/scene/LN_spawn_collection.py | 2 +- blender/arm/logicnode/sound/LN_play_sound.py | 17 +- .../arm/logicnode/string/LN_string_case.py | 3 +- .../logicnode/string/LN_string_contains.py | 3 +- blender/arm/logicnode/trait/LN_trait.py | 2 +- .../transform/LN_get_world_orientation.py | 3 +- blender/arm/logicnode/transform/LN_look_at.py | 3 +- .../logicnode/transform/LN_rotate_object.py | 13 +- .../transform/LN_set_object_rotation.py | 3 +- .../transform/LN_transform_to_vector.py | 3 +- blender/arm/make_logic.py | 34 +-- blender/arm/node_utils.py | 46 ++- 73 files changed, 565 insertions(+), 238 deletions(-) create mode 100644 blender/arm/logicnode/arm_props.py diff --git a/Sources/armory/logicnode/AddRigidBodyNode.hx b/Sources/armory/logicnode/AddRigidBodyNode.hx index a599e568..d896c2e0 100644 --- a/Sources/armory/logicnode/AddRigidBodyNode.hx +++ b/Sources/armory/logicnode/AddRigidBodyNode.hx @@ -9,7 +9,7 @@ import armory.trait.physics.RigidBody; class AddRigidBodyNode extends LogicNode { public var property0: String;//Shape - public var property1: String;//Advanced + public var property1: Bool;//Advanced public var object: Object; public function new(tree: LogicTree) { @@ -38,8 +38,7 @@ class AddRigidBodyNode extends LogicNode { var shape: Shape = 1; - if(property1 == 'true') - { + if (property1) { margin = inputs[9].get(); marginLen = inputs[10].get(); linDamp = inputs[11].get(); @@ -49,34 +48,29 @@ class AddRigidBodyNode extends LogicNode { angVelThreshold = inputs[15].get(); group = inputs[16].get(); mask = inputs[17].get(); - } - if (object == null) return; #if arm_physics var rb: RigidBody = object.getTrait(RigidBody); - if((group < 0) || (group > 32)) group = 1; //Limiting max groups to 32 - if((mask < 0) || (mask > 32)) mask = 1; //Limiting max masks to 32 - if(rb == null) - { - + if ((group < 0) || (group > 32)) group = 1; //Limiting max groups to 32 + if ((mask < 0) || (mask > 32)) mask = 1; //Limiting max masks to 32 + if (rb == null) { switch (property0){ - - case 'Box': + case "Box": shape = Box; - case 'Sphere': + case "Sphere": shape = Sphere; - case 'Capsule': + case "Capsule": shape = Capsule; - case 'Cone': + case "Cone": shape = Cone; - case 'Cylinder': + case "Cylinder": shape = Cylinder; - case 'Convex Hull': + case "Convex Hull": shape = ConvexHull; - case 'Mesh': + case "Mesh": shape = Mesh; } @@ -84,15 +78,14 @@ class AddRigidBodyNode extends LogicNode { rb.animated = animated; rb.staticObj = ! active; rb.isTriggerObject(trigger); - if(property1 == 'true') - { + + if (property1) { rb.linearDamping = linDamp; rb.angularDamping = angDamp; - if(margin) rb.collisionMargin = marginLen; - if(useDeactiv) { + if (margin) rb.collisionMargin = marginLen; + if (useDeactiv) { rb.setUpDeactivation(true, linearVelThreshold, angVelThreshold, 0.0); } - } object.addTrait(rb); diff --git a/Sources/armory/logicnode/MathNode.hx b/Sources/armory/logicnode/MathNode.hx index 4905c47e..5adc477d 100644 --- a/Sources/armory/logicnode/MathNode.hx +++ b/Sources/armory/logicnode/MathNode.hx @@ -3,7 +3,7 @@ package armory.logicnode; class MathNode extends LogicNode { public var property0: String; // Operation - public var property1: String; // Clamp + public var property1: Bool; // Clamp public function new(tree: LogicTree) { super(tree); @@ -80,8 +80,8 @@ class MathNode extends LogicNode { } } // Clamp - if (property1 == "true") r = r < 0.0 ? 0.0 : (r > 1.0 ? 1.0 : r); + if (property1) r = r < 0.0 ? 0.0 : (r > 1.0 ? 1.0 : r); return r; } -} \ No newline at end of file +} diff --git a/Sources/armory/logicnode/MixNode.hx b/Sources/armory/logicnode/MixNode.hx index 90f51bea..4f75884d 100644 --- a/Sources/armory/logicnode/MixNode.hx +++ b/Sources/armory/logicnode/MixNode.hx @@ -6,7 +6,7 @@ class MixNode extends LogicNode { public var property0: String; // Type public var property1: String; // Ease - public var property2: String; // Clamp + public var property2: Bool; // Clamp var ease: Float->Float = null; @@ -50,7 +50,9 @@ class MixNode extends LogicNode { var v2: Float = inputs[2].get(); var f = v1 + (v2 - v1) * ease(k); - if (property2 == "true") f = f < 0 ? 0 : f > 1 ? 1 : f; + // Clamp + if (property2) f = f < 0 ? 0 : f > 1 ? 1 : f; + return f; } } diff --git a/Sources/armory/logicnode/PhysicsConstraintNode.hx b/Sources/armory/logicnode/PhysicsConstraintNode.hx index 5446788b..e6bd0bf4 100644 --- a/Sources/armory/logicnode/PhysicsConstraintNode.hx +++ b/Sources/armory/logicnode/PhysicsConstraintNode.hx @@ -3,15 +3,14 @@ package armory.logicnode; #if arm_physics import armory.trait.physics.bullet.PhysicsConstraint.ConstraintAxis; #end -import iron.object.Object; class PhysicsConstraintNode extends LogicNode { - public var property0: String;//Linear or Angular - public var property1: String;//Axis - public var property2: String;//Is a spring - public var value1: Float;//Lower limit or Spring Stiffness - public var value2: Float;//Upper limit or Spring Damping + public var property0: String; //Linear or Angular + public var property1: String; //Axis + public var property2: Bool; //Is a spring + public var value1: Float; //Lower limit or Spring Stiffness + public var value2: Float; //Upper limit or Spring Damping public var isAngular: Bool; public var axis: ConstraintAxis; public var isSpring: Bool; @@ -24,27 +23,13 @@ class PhysicsConstraintNode extends LogicNode { value1 = inputs[0].get(); value2 = inputs[1].get(); - if(property0 == 'Linear') { - isAngular = false; - } - else{ - isAngular = true; - } - - if(property2 == 'true'){ - isSpring = true; - } - else { - isSpring = false; - } + isAngular = property0 != "Linear"; + isSpring = property2; switch (property1){ - case 'X': - axis = X; - case 'Y': - axis = Y; - case 'Z': - axis = Z; + case "X": axis = X; + case "Y": axis = Y; + case "Z": axis = Z; } return this; diff --git a/Sources/armory/logicnode/VectorMixNode.hx b/Sources/armory/logicnode/VectorMixNode.hx index 47286ac7..ae2b7938 100644 --- a/Sources/armory/logicnode/VectorMixNode.hx +++ b/Sources/armory/logicnode/VectorMixNode.hx @@ -7,7 +7,7 @@ class VectorMixNode extends LogicNode { public var property0: String; // Type public var property1: String; // Ease - public var property2: String; // Clamp + public var property2: Bool; // Clamp var v = new Vec4(); @@ -57,7 +57,7 @@ class VectorMixNode extends LogicNode { v.y = v1.y + (v2.y - v1.y) * f; v.z = v1.z + (v2.z - v1.z) * f; - if (property2 == "true") v.clamp(0, 1); + if (property2) v.clamp(0, 1); return v; } } diff --git a/Sources/armory/trait/internal/LivePatch.hx b/Sources/armory/trait/internal/LivePatch.hx index 49c4cd26..8d8be7db 100644 --- a/Sources/armory/trait/internal/LivePatch.hx +++ b/Sources/armory/trait/internal/LivePatch.hx @@ -45,5 +45,15 @@ class LivePatch extends iron.Trait { } toNode.inputs[toIndex] = new LogicNodeInput(fromNode, fromIndex); } + + public static function patchUpdateNodeProp(treeName: String, nodeName: String, propName: String, value: Dynamic) { + var tree = LogicTree.nodeTrees[treeName]; + if (tree == null) return; + + var node = tree.nodes[nodeName]; + if (node == null) return; + + Reflect.setField(node, propName, value); + } #end } diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index 1403d58c..ce9f8f50 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -153,8 +153,7 @@ def send_event(event_id: str, opt_data: Any = None): # This event is called twice for a connection but we only need # send it once if node == link.from_node: - node_tree = node.get_tree() - tree_name = arm.node_utils.get_export_tree_name(node_tree) + tree_name = arm.node_utils.get_export_tree_name(node.get_tree()) # [1:] is used here because make_logic already uses that for # node names if arm_debug is used @@ -167,6 +166,18 @@ def send_event(event_id: str, opt_data: Any = None): js = f'LivePatch.patchCreateNodeLink("{tree_name}", "{from_node_name}", "{to_node_name}", "{from_index}", "{to_index}");' write_patch(js) + if event_id == 'ln_update_prop': + node: ArmLogicTreeNode + prop_name: str + node, prop_name = opt_data + + tree_name = arm.node_utils.get_export_tree_name(node.get_tree()) + node_name = arm.node_utils.get_export_node_name(node)[1:] + + value = arm.node_utils.haxe_format_prop(node, prop_name) + + js = f'LivePatch.patchUpdateNodeProp("{tree_name}", "{node_name}", "{prop_name}", {value});' + write_patch(js) def on_operator(operator_id: str): diff --git a/blender/arm/logicnode/arm_nodes.py b/blender/arm/logicnode/arm_nodes.py index 1a0dad15..152901c3 100644 --- a/blender/arm/logicnode/arm_nodes.py +++ b/blender/arm/logicnode/arm_nodes.py @@ -1,6 +1,6 @@ import itertools from collections import OrderedDict -from typing import Any, Generator, List, Optional, Type, Dict +from typing import Any, Generator, List, Optional, Type from typing import OrderedDict as ODict # Prevent naming conflicts import bpy.types @@ -8,7 +8,9 @@ from bpy.props import * from nodeitems_utils import NodeItem import arm # we cannot import arm.livepatch here or we have a circular import -# Pass NodeReplacement forward to individual node modules that import arm_nodes +# Pass custom property types and NodeReplacement forward to individual +# node modules that import arm_nodes +from arm.logicnode.arm_props import * from arm.logicnode.replacement import NodeReplacement import arm.node_utils @@ -52,6 +54,13 @@ class ArmLogicTreeNode(bpy.types.Node): def get_tree(self): return self.id_data + def on_prop_update(self, context: bpy.types.Context, prop_name: str): + """Called if a property created with a function from the + arm_props module is changed. If the property has a custom update + function, it is called before `on_prop_update()`. + """ + arm.live_patch.send_event('ln_update_prop', (self, prop_name)) + def insert_link(self, link: bpy.types.NodeLink): """Called on *both* nodes when a link between two nodes is created.""" arm.live_patch.send_event('ln_insert_link', (self, link)) diff --git a/blender/arm/logicnode/arm_props.py b/blender/arm/logicnode/arm_props.py new file mode 100644 index 00000000..c2777689 --- /dev/null +++ b/blender/arm/logicnode/arm_props.py @@ -0,0 +1,267 @@ +"""Custom bpy property creators for logic nodes. Please be aware that +the code in this file is usually run once at registration and not for +each individual node instance when it is created. + +The functions for creating typed properties wrap the private __haxe_prop +function to allow for IDE autocompletion. + +Some default parameters in the signature of functions in this module are +mutable (common Python pitfall, be aware of this!), but because they +don't get accessed later it doesn't matter here and we keep it this way +for parity with the Blender API. +""" +from typing import Any, Callable, Sequence, Union + +import bpy +from bpy.props import * + +# Property parameter name `set` shadows built-in type `set` +__set = set + + +def __haxe_prop(prop_type: Callable, prop_name: str, *args, **kwargs) -> Any: + """Declares a logic node property as a property that will be + used ingame for a logic node.""" + update_callback: Callable = kwargs.get('update', None) + if update_callback is None: + def wrapper(self: bpy.types.Node, context: bpy.types.Context): + self.on_prop_update(context, prop_name) + kwargs['update'] = wrapper + else: + def wrapper(self: bpy.types.Node, context: bpy.types.Context): + update_callback(self, context) + self.on_prop_update(context, prop_name) + kwargs['update'] = wrapper + + # Tags are not allowed on classes other than bpy.types.ID or + # bpy.types.Bone, remove them here to prevent registration errors + if 'tags' in kwargs: + del kwargs['tags'] + + return prop_type(*args, **kwargs) + + +def HaxeBoolProperty( + prop_name: str, + *, # force passing further arguments as keywords, see PEP 3102 + name: str = "", + description: str = "", + default=False, + options: set = {'ANIMATABLE'}, + override: set = set(), + tags: set = set(), + subtype: str = 'NONE', + update=None, + get=None, + set=None +) -> 'bpy.types.BoolProperty': + """Declares a new BoolProperty that has a counterpart with the given + prop_name (Python and Haxe names must be identical for now). + """ + return __haxe_prop(BoolProperty, **locals()) + + +def HaxeBoolVectorProperty( + prop_name: str, + *, + name: str = "", + description: str = "", + default: list = (False, False, False), + options: set = {'ANIMATABLE'}, + override: set = set(), + tags: set = set(), + subtype: str = 'NONE', + size: int = 3, + update=None, + get=None, + set=None +) -> list['bpy.types.BoolProperty']: + """Declares a new BoolVectorProperty that has a counterpart with the + given prop_name (Python and Haxe names must be identical for now). + """ + return __haxe_prop(BoolVectorProperty, **locals()) + + +def HaxeCollectionProperty( + prop_name: str, + *, + type=None, + name: str = "", + description: str = "", + options: set = {'ANIMATABLE'}, + override: set = set(), + tags: set = set() +) -> 'bpy.types.CollectionProperty': + """Declares a new CollectionProperty that has a counterpart with the + given prop_name (Python and Haxe names must be identical for now). + """ + return __haxe_prop(CollectionProperty, **locals()) + + +def HaxeEnumProperty( + prop_name: str, + *, + items: Sequence, + name: str = "", + description: str = "", + default: Union[str, set[str]] = None, + options: set = {'ANIMATABLE'}, + override: set = set(), + tags: set = set(), + update=None, + get=None, + set=None +) -> 'bpy.types.EnumProperty': + """Declares a new EnumProperty that has a counterpart with the given + prop_name (Python and Haxe names must be identical for now). + """ + return __haxe_prop(EnumProperty, **locals()) + + +def HaxeFloatProperty( + prop_name: str, + *, + name: str = "", + description: str = "", + default=0.0, + min: float = -3.402823e+38, + max: float = 3.402823e+38, + soft_min: float = -3.402823e+38, + soft_max: float = 3.402823e+38, + step: int = 3, + precision: int = 2, + options: set = {'ANIMATABLE'}, + override: set = set(), + tags: set = set(), + subtype: str = 'NONE', + unit: str = 'NONE', + update=None, + get=None, + set=None +) -> 'bpy.types.FloatProperty': + """Declares a new FloatProperty that has a counterpart with the + given prop_name (Python and Haxe names must be identical for now). + """ + return __haxe_prop(FloatProperty, **locals()) + + +def HaxeFloatVectorProperty( + prop_name: str, + *, + name: str = "", + description: str = "", + default: list = (0.0, 0.0, 0.0), + min: float = 'sys.float_info.min', + max: float = 'sys.float_info.max', + soft_min: float = 'sys.float_info.min', + soft_max: float = 'sys.float_info.max', + step: int = 3, + precision: int = 2, + options: set = {'ANIMATABLE'}, + override: set = set(), + tags: set = set(), + subtype: str = 'NONE', + unit: str = 'NONE', + size: int = 3, + update=None, + get=None, + set=None +) -> list['bpy.types.FloatProperty']: + """Declares a new FloatVectorProperty that has a counterpart with the + given prop_name (Python and Haxe names must be identical for now). + """ + return __haxe_prop(FloatVectorProperty, **locals()) + + +def HaxeIntProperty( + prop_name: str, + *, + name: str = "", + description: str = "", + default=0, + min: int = -2**31, + max: int = 2**31 - 1, + soft_min: int = -2**31, + soft_max: int = 2**31 - 1, + step: int = 1, + options: set = {'ANIMATABLE'}, + override: set = set(), + tags: set = set(), + subtype: str = 'NONE', + update=None, + get=None, + set=None +) -> 'bpy.types.IntProperty': + """Declares a new IntProperty that has a counterpart with the given + prop_name (Python and Haxe names must be identical for now). + """ + return __haxe_prop(IntProperty, **locals()) + + +def HaxeIntVectorProperty( + prop_name: str, + *, + name: str = "", + description: str = "", + default: list = (0, 0, 0), + min: int = -2**31, + max: int = 2**31 - 1, + soft_min: int = -2**31, + soft_max: int = 2**31 - 1, + step: int = 1, + options: set = {'ANIMATABLE'}, + override: set = set(), + tags: set = set(), + subtype: str = 'NONE', + size: int = 3, + update=None, + get=None, + set=None +) -> list['bpy.types.IntProperty']: + """Declares a new IntVectorProperty that has a counterpart with the given + prop_name (Python and Haxe names must be identical for now). + """ + return __haxe_prop(IntVectorProperty, **locals()) + + +def HaxePointerProperty( + prop_name: str, + *, + type=None, + name: str = "", + description: str = "", + options: set = {'ANIMATABLE'}, + override: set = set(), + tags: set = set(), + poll=None, + update=None +) -> 'bpy.types.PointerProperty': + """Declares a new PointerProperty that has a counterpart with the + given prop_name (Python and Haxe names must be identical for now). + """ + return __haxe_prop(PointerProperty, **locals()) + + +def RemoveHaxeProperty(cls, attr: str): + RemoveProperty(cls, attr) + + +def HaxeStringProperty( + prop_name: str, + *, + name: str = "", + description: str = "", + default: str = "", + maxlen: int = 0, + options: set = {'ANIMATABLE'}, + override: set = set(), + tags: set = set(), + subtype: str = 'NONE', + update=None, + get=None, + set=None +) -> 'bpy.types.StringProperty': + """Declares a new StringProperty that has a counterpart with the + given prop_name (Python and Haxe names must be identical for now). + """ + return __haxe_prop(StringProperty, **locals()) diff --git a/blender/arm/logicnode/canvas/LN_on_canvas_element.py b/blender/arm/logicnode/canvas/LN_on_canvas_element.py index 4eab0872..ba1301c1 100644 --- a/blender/arm/logicnode/canvas/LN_on_canvas_element.py +++ b/blender/arm/logicnode/canvas/LN_on_canvas_element.py @@ -6,16 +6,19 @@ class OnCanvasElementNode(ArmLogicTreeNode): bl_label = 'On Canvas Element' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items=[('click', 'Click', 'Listen to mouse clicks'), ('hover', 'Hover', 'Listen to mouse hover')], name='Listen to', default='click') - property1: EnumProperty( + property1: HaxeEnumProperty( + 'property1', items=[('started', 'Started', 'Started'), ('down', 'Down', 'Down'), ('released', 'Released', 'Released')], name='Status', default='started') - property2: EnumProperty( + property2: HaxeEnumProperty( + 'property2', items=[('left', 'Left', 'Left mouse button'), ('middle', 'Middle', 'Middle mouse button'), ('right', 'Right', 'Right mouse button')], diff --git a/blender/arm/logicnode/deprecated/LN_on_gamepad.py b/blender/arm/logicnode/deprecated/LN_on_gamepad.py index e2867026..39433482 100644 --- a/blender/arm/logicnode/deprecated/LN_on_gamepad.py +++ b/blender/arm/logicnode/deprecated/LN_on_gamepad.py @@ -11,7 +11,8 @@ class OnGamepadNode(ArmLogicTreeNode): arm_section = 'gamepad' arm_version = 2 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Down', 'Down', 'Down'), ('Started', 'Started', 'Started'), ('Released', 'Released', 'Released')], @@ -19,7 +20,8 @@ class OnGamepadNode(ArmLogicTreeNode): # ('Moved Right', 'Moved Right', 'Moved Right'),], name='', default='Started') - property1: EnumProperty( + property1: HaxeEnumProperty( + 'property1', items = [('cross', 'cross / a', 'cross / a'), ('circle', 'circle / b', 'circle / b'), ('square', 'square / x', 'square / x'), diff --git a/blender/arm/logicnode/deprecated/LN_on_keyboard.py b/blender/arm/logicnode/deprecated/LN_on_keyboard.py index 29d6d2a3..b5b8bbcf 100644 --- a/blender/arm/logicnode/deprecated/LN_on_keyboard.py +++ b/blender/arm/logicnode/deprecated/LN_on_keyboard.py @@ -11,13 +11,15 @@ class OnKeyboardNode(ArmLogicTreeNode): arm_section = 'keyboard' arm_version = 2 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Down', 'Down', 'Down'), ('Started', 'Started', 'Started'), ('Released', 'Released', 'Released')], name='', default='Started') - property1: EnumProperty( + property1: HaxeEnumProperty( + 'property1', items = [('a', 'a', 'a'), ('b', 'b', 'b'), ('c', 'c', 'c'), diff --git a/blender/arm/logicnode/deprecated/LN_on_mouse.py b/blender/arm/logicnode/deprecated/LN_on_mouse.py index c4ac60b9..487d634e 100644 --- a/blender/arm/logicnode/deprecated/LN_on_mouse.py +++ b/blender/arm/logicnode/deprecated/LN_on_mouse.py @@ -11,13 +11,15 @@ class OnMouseNode(ArmLogicTreeNode): arm_section = 'mouse' arm_version = 2 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Down', 'Down', 'Down'), ('Started', 'Started', 'Started'), ('Released', 'Released', 'Released'), ('Moved', 'Moved', 'Moved')], name='', default='Down') - property1: EnumProperty( + property1: HaxeEnumProperty( + 'property1', items = [('left', 'left', 'left'), ('right', 'right', 'right'), ('middle', 'middle', 'middle')], diff --git a/blender/arm/logicnode/deprecated/LN_on_surface.py b/blender/arm/logicnode/deprecated/LN_on_surface.py index 066eb4a6..b906d143 100644 --- a/blender/arm/logicnode/deprecated/LN_on_surface.py +++ b/blender/arm/logicnode/deprecated/LN_on_surface.py @@ -11,7 +11,8 @@ class OnSurfaceNode(ArmLogicTreeNode): arm_section = 'surface' arm_version = 2 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Touched', 'Touched', 'Touched'), ('Started', 'Started', 'Started'), ('Released', 'Released', 'Released'), diff --git a/blender/arm/logicnode/deprecated/LN_on_virtual_button.py b/blender/arm/logicnode/deprecated/LN_on_virtual_button.py index 21ba0382..e5201612 100644 --- a/blender/arm/logicnode/deprecated/LN_on_virtual_button.py +++ b/blender/arm/logicnode/deprecated/LN_on_virtual_button.py @@ -11,12 +11,13 @@ class OnVirtualButtonNode(ArmLogicTreeNode): arm_section = 'virtual' arm_version = 2 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Down', 'Down', 'Down'), ('Started', 'Started', 'Started'), ('Released', 'Released', 'Released')], name='', default='Started') - property1: StringProperty(name='', default='button') + property1: HaxeStringProperty('property1', name='', default='button') def init(self, context): super(OnVirtualButtonNode, self).init(context) diff --git a/blender/arm/logicnode/event/LN_on_event.py b/blender/arm/logicnode/event/LN_on_event.py index 0d3df334..99d68c06 100644 --- a/blender/arm/logicnode/event/LN_on_event.py +++ b/blender/arm/logicnode/event/LN_on_event.py @@ -10,7 +10,7 @@ class OnEventNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'custom' - property0: StringProperty(name='', default='') + property0: HaxeStringProperty('property0', name='', default='') def init(self, context): super(OnEventNode, self).init(context) diff --git a/blender/arm/logicnode/event/LN_on_update.py b/blender/arm/logicnode/event/LN_on_update.py index df185213..3b39d066 100644 --- a/blender/arm/logicnode/event/LN_on_update.py +++ b/blender/arm/logicnode/event/LN_on_update.py @@ -10,7 +10,8 @@ class OnUpdateNode(ArmLogicTreeNode): bl_idname = 'LNOnUpdateNode' bl_label = 'On Update' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Update', 'Update', 'Update'), ('Late Update', 'Late Update', 'Late Update'), ('Physics Pre-Update', 'Physics Pre-Update', 'Physics Pre-Update')], diff --git a/blender/arm/logicnode/input/LN_gamepad.py b/blender/arm/logicnode/input/LN_gamepad.py index b27e705b..df8dec2b 100644 --- a/blender/arm/logicnode/input/LN_gamepad.py +++ b/blender/arm/logicnode/input/LN_gamepad.py @@ -15,7 +15,8 @@ class GamepadNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'gamepad' - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('started', 'Started', 'The gamepad button starts to be pressed'), ('down', 'Down', 'The gamepad button is pressed'), ('released', 'Released', 'The gamepad button stops being pressed')], @@ -23,7 +24,8 @@ class GamepadNode(ArmLogicTreeNode): # ('Moved Right', 'Moved Right', 'Moved Right'),], name='', default='down') - property1: EnumProperty( + property1: HaxeEnumProperty( + 'property1', items = [('cross', 'cross / a', 'cross / a'), ('circle', 'circle / b', 'circle / b'), ('square', 'square / x', 'square / x'), diff --git a/blender/arm/logicnode/input/LN_keyboard.py b/blender/arm/logicnode/input/LN_keyboard.py index 8e04c8df..230425db 100644 --- a/blender/arm/logicnode/input/LN_keyboard.py +++ b/blender/arm/logicnode/input/LN_keyboard.py @@ -7,13 +7,15 @@ class KeyboardNode(ArmLogicTreeNode): arm_section = 'keyboard' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('started', 'Started', 'The keyboard button starts to be pressed'), ('down', 'Down', 'The keyboard button is pressed'), ('released', 'Released', 'The keyboard button stops being pressed')], name='', default='down') - property1: EnumProperty( + property1: HaxeEnumProperty( + 'property1', items = [('a', 'a', 'a'), ('b', 'b', 'b'), ('c', 'c', 'c'), diff --git a/blender/arm/logicnode/input/LN_mouse.py b/blender/arm/logicnode/input/LN_mouse.py index cfdb052c..3152a132 100644 --- a/blender/arm/logicnode/input/LN_mouse.py +++ b/blender/arm/logicnode/input/LN_mouse.py @@ -7,13 +7,15 @@ class MouseNode(ArmLogicTreeNode): arm_section = 'mouse' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('started', 'Started', 'The mouse button startes to be pressed'), ('down', 'Down', 'The mouse button is pressed'), ('released', 'Released', 'The mouse button stops being pressed'), ('moved', 'Moved', 'Moved')], name='', default='down') - property1: EnumProperty( + property1: HaxeEnumProperty( + 'property1', items = [('left', 'Left', 'Left mouse button'), ('middle', 'Middle', 'Middle mouse button'), ('right', 'Right', 'Right mouse button')], diff --git a/blender/arm/logicnode/input/LN_set_cursor_state.py b/blender/arm/logicnode/input/LN_set_cursor_state.py index ac83802c..15f280cb 100644 --- a/blender/arm/logicnode/input/LN_set_cursor_state.py +++ b/blender/arm/logicnode/input/LN_set_cursor_state.py @@ -14,7 +14,8 @@ class SetCursorStateNode(ArmLogicTreeNode): arm_section = 'mouse' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('hide locked', 'Hide Locked', 'The mouse cursor is hidden and locked'), ('hide', 'Hide', 'The mouse cursor is hidden'), ('lock', 'Lock', 'The mouse cursor is locked'), diff --git a/blender/arm/logicnode/input/LN_touch.py b/blender/arm/logicnode/input/LN_touch.py index a5774ea5..2beb6ac1 100644 --- a/blender/arm/logicnode/input/LN_touch.py +++ b/blender/arm/logicnode/input/LN_touch.py @@ -7,7 +7,8 @@ class SurfaceNode(ArmLogicTreeNode): arm_section = 'surface' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('started', 'Started', 'The screen surface starts to be touched'), ('down', 'Down', 'The screen surface is touched'), ('released', 'Released', 'The screen surface stops being touched'), diff --git a/blender/arm/logicnode/input/LN_virtual_button.py b/blender/arm/logicnode/input/LN_virtual_button.py index d2bce52f..9d4c9c3f 100644 --- a/blender/arm/logicnode/input/LN_virtual_button.py +++ b/blender/arm/logicnode/input/LN_virtual_button.py @@ -7,12 +7,13 @@ class VirtualButtonNode(ArmLogicTreeNode): arm_section = 'virtual' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('started', 'Started', 'The virtual button starts to be pressed'), ('down', 'Down', 'The virtual button is pressed'), ('released', 'Released', 'The virtual button stops being pressed')], name='', default='down') - property1: StringProperty(name='', default='button') + property1: HaxeStringProperty('property1', name='', default='button') def init(self, context): super(VirtualButtonNode, self).init(context) diff --git a/blender/arm/logicnode/logic/LN_gate.py b/blender/arm/logicnode/logic/LN_gate.py index 5fbc6818..ad931f7c 100644 --- a/blender/arm/logicnode/logic/LN_gate.py +++ b/blender/arm/logicnode/logic/LN_gate.py @@ -20,7 +20,8 @@ class GateNode(ArmLogicTreeNode): arm_version = 1 min_inputs = 3 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Equal', 'Equal', 'Equal'), ('Almost Equal', 'Almost Equal', 'Almost Equal'), ('Greater', 'Greater', 'Greater'), @@ -31,7 +32,7 @@ class GateNode(ArmLogicTreeNode): ('And', 'And', 'And')], name='', default='Equal', update=remove_extra_inputs) - property1: FloatProperty(name='Tolerance', description='Precision for float compare', default=0.0001) + property1: HaxeFloatProperty('property1', name='Tolerance', description='Precision for float compare', default=0.0001) def __init__(self): super(GateNode, self).__init__() diff --git a/blender/arm/logicnode/material/LN_material.py b/blender/arm/logicnode/material/LN_material.py index 03e923b6..7b3a4cc0 100644 --- a/blender/arm/logicnode/material/LN_material.py +++ b/blender/arm/logicnode/material/LN_material.py @@ -18,7 +18,7 @@ class MaterialNode(ArmLogicTreeNode): return self.property0.name return arm.utils.asset_name(bpy.data.materials[self.property0.name]) - property0: PointerProperty(name='', type=bpy.types.Material) + property0: HaxePointerProperty('property0', name='', type=bpy.types.Material) def init(self, context): super(MaterialNode, self).init(context) diff --git a/blender/arm/logicnode/math/LN_compare.py b/blender/arm/logicnode/math/LN_compare.py index 7b1a59ef..3081376d 100644 --- a/blender/arm/logicnode/math/LN_compare.py +++ b/blender/arm/logicnode/math/LN_compare.py @@ -10,7 +10,8 @@ class CompareNode(ArmLogicTreeNode): bl_idname = 'LNCompareNode' bl_label = 'Compare' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Equal', 'Equal', 'Equal'), ('Almost Equal', 'Almost Equal', 'Almost Equal'), ('Greater', 'Greater', 'Greater'), @@ -22,7 +23,7 @@ class CompareNode(ArmLogicTreeNode): name='', default='Equal', update=remove_extra_inputs) min_inputs = 2 - property1: FloatProperty(name='Tolerance', description='Precision for float compare', default=0.0001) + property1: HaxeFloatProperty('property1', name='Tolerance', description='Precision for float compare', default=0.0001) def __init__(self): super(CompareNode, self).__init__() diff --git a/blender/arm/logicnode/math/LN_math.py b/blender/arm/logicnode/math/LN_math.py index 3d8c4796..e090811f 100644 --- a/blender/arm/logicnode/math/LN_math.py +++ b/blender/arm/logicnode/math/LN_math.py @@ -64,7 +64,8 @@ class MathNode(ArmLogicTreeNode): self.inputs.remove(self.inputs.values()[-1]) self['property0'] = value - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Add', 'Add', 'Add'), ('Multiply', 'Multiply', 'Multiply'), ('Sine', 'Sine', 'Sine'), @@ -92,11 +93,7 @@ class MathNode(ArmLogicTreeNode): ('Exponent', 'Exponent', 'Exponent')], name='', default='Add', set=set_enum, get=get_enum) - @property - def property1(self): - return 'true' if self.property1_ else 'false' - - property1_: BoolProperty(name='Clamp', default=False) + property1: HaxeBoolProperty('property1', name='Clamp', default=False) def __init__(self): array_nodes[str(id(self))] = self @@ -109,7 +106,7 @@ class MathNode(ArmLogicTreeNode): self.add_output('NodeSocketFloat', 'Result') def draw_buttons(self, context, layout): - layout.prop(self, 'property1_') + layout.prop(self, 'property1') layout.prop(self, 'property0') # Many arguments: Add, Subtract, Multiply, Divide if (self.get_count_in(self.property0) == 0): diff --git a/blender/arm/logicnode/math/LN_math_expression.py b/blender/arm/logicnode/math/LN_math_expression.py index b2fd3392..923b6aee 100644 --- a/blender/arm/logicnode/math/LN_math_expression.py +++ b/blender/arm/logicnode/math/LN_math_expression.py @@ -155,8 +155,8 @@ class MathExpressionNode(ArmLogicTreeNode): def get_exp(self): return self.get('property0', 'a + b') - property0: StringProperty(name='', description='Expression (operation: +, -, *, /, ^, (, ), %)', set=set_exp, get=get_exp) - property1: BoolProperty(name='Clamp', default=False) + property0: HaxeStringProperty('property0', name='', description='Expression (operation: +, -, *, /, ^, (, ), %)', set=set_exp, get=get_exp) + property1: HaxeBoolProperty('property1', name='Clamp', default=False) def __init__(self): array_nodes[str(id(self))] = self diff --git a/blender/arm/logicnode/math/LN_matrix_math.py b/blender/arm/logicnode/math/LN_matrix_math.py index 3a2fc6f4..4a05ce3d 100644 --- a/blender/arm/logicnode/math/LN_matrix_math.py +++ b/blender/arm/logicnode/math/LN_matrix_math.py @@ -7,7 +7,8 @@ class MatrixMathNode(ArmLogicTreeNode): arm_section = 'matrix' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Multiply', 'Multiply', 'Multiply')], name='', default='Multiply') diff --git a/blender/arm/logicnode/math/LN_mix.py b/blender/arm/logicnode/math/LN_mix.py index 7924c9c3..92fa80df 100644 --- a/blender/arm/logicnode/math/LN_mix.py +++ b/blender/arm/logicnode/math/LN_mix.py @@ -5,7 +5,8 @@ class MixNode(ArmLogicTreeNode): bl_idname = 'LNMixNode' bl_label = 'Mix' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Linear', 'Linear', 'Linear'), ('Sine', 'Sine', 'Sine'), ('Quad', 'Quad', 'Quad'), @@ -19,18 +20,15 @@ class MixNode(ArmLogicTreeNode): ('Elastic', 'Elastic', 'Elastic'), ], name='', default='Linear') - property1: EnumProperty( + property1: HaxeEnumProperty( + 'property1', items = [('In', 'In', 'In'), ('Out', 'Out', 'Out'), ('InOut', 'InOut', 'InOut'), ], name='', default='Out') - @property - def property2(self): - return 'true' if self.property2_ else 'false' - - property2_: BoolProperty(name='Clamp', default=False) + property2: HaxeBoolProperty('property2', name='Clamp', default=False) def init(self, context): super(MixNode, self).init(context) @@ -41,6 +39,6 @@ class MixNode(ArmLogicTreeNode): self.add_output('NodeSocketFloat', 'Result') def draw_buttons(self, context, layout): - layout.prop(self, 'property2_') + layout.prop(self, 'property2') layout.prop(self, 'property0') layout.prop(self, 'property1') diff --git a/blender/arm/logicnode/math/LN_mix_vector.py b/blender/arm/logicnode/math/LN_mix_vector.py index 31c7f587..cbab381b 100644 --- a/blender/arm/logicnode/math/LN_mix_vector.py +++ b/blender/arm/logicnode/math/LN_mix_vector.py @@ -7,7 +7,8 @@ class VectorMixNode(ArmLogicTreeNode): arm_section = 'vector' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Linear', 'Linear', 'Linear'), ('Sine', 'Sine', 'Sine'), ('Quad', 'Quad', 'Quad'), @@ -21,18 +22,15 @@ class VectorMixNode(ArmLogicTreeNode): ('Elastic', 'Elastic', 'Elastic'), ], name='', default='Linear') - property1: EnumProperty( + property1: HaxeEnumProperty( + 'property1', items = [('In', 'In', 'In'), ('Out', 'Out', 'Out'), ('InOut', 'InOut', 'InOut'), ], name='', default='Out') - @property - def property2(self): - return 'true' if self.property2_ else 'false' - - property2_: BoolProperty(name='Clamp', default=False) + property2: HaxeBoolProperty('property2', name='Clamp', default=False) def init(self, context): super(VectorMixNode, self).init(context) @@ -43,7 +41,7 @@ class VectorMixNode(ArmLogicTreeNode): self.add_output('NodeSocketVector', 'Result') def draw_buttons(self, context, layout): - layout.prop(self, 'property2_') + layout.prop(self, 'property2') layout.prop(self, 'property0') if self.property0 != 'Linear': layout.prop(self, 'property1') diff --git a/blender/arm/logicnode/math/LN_quaternion_math.py b/blender/arm/logicnode/math/LN_quaternion_math.py index d1f91abc..093b8c83 100644 --- a/blender/arm/logicnode/math/LN_quaternion_math.py +++ b/blender/arm/logicnode/math/LN_quaternion_math.py @@ -41,7 +41,7 @@ class QuaternionMathNode(ArmLogicTreeNode): if (self.property0 == 'ToAxisAngle'): self.add_output('NodeSocketFloat', 'To Axis Angle') # ToAxisAngle - property1: BoolProperty(name='Separator Out', default=False, set=set_bool, get=get_bool) + property1: HaxeBoolProperty('property1', name='Separator Out', default=False, set=set_bool, get=get_bool) @staticmethod def get_enum_id_value(obj, prop_name, value): @@ -132,7 +132,8 @@ class QuaternionMathNode(ArmLogicTreeNode): self.add_output('NodeSocketFloat', 'Module') self['property0'] = value - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Add', 'Add', 'Add'), ('Subtract', 'Subtract', 'Subtract'), ('DotProduct', 'Dot Product', 'Dot Product'), diff --git a/blender/arm/logicnode/math/LN_screen_to_world_space.py b/blender/arm/logicnode/math/LN_screen_to_world_space.py index 52b6fa36..b01ac4e1 100644 --- a/blender/arm/logicnode/math/LN_screen_to_world_space.py +++ b/blender/arm/logicnode/math/LN_screen_to_world_space.py @@ -10,12 +10,7 @@ class ScreenToWorldSpaceNode(ArmLogicTreeNode): min_outputs = 2 max_outputs = 8 - # Separator - @property - def property0(self): - return True if self.property0_ else False - - property0_: BoolProperty(name='Separator Out', default=False) + property0: HaxeBoolProperty('property0', name='Separator Out', default=False) def init(self, context): super(ScreenToWorldSpaceNode, self).init(context) @@ -26,8 +21,8 @@ class ScreenToWorldSpaceNode(ArmLogicTreeNode): self.add_output('NodeSocketVector', 'Direction') def draw_buttons(self, context, layout): - layout.prop(self, 'property0_') # Separator Out - if self.property0_: + layout.prop(self, 'property0') # Separator Out + if self.property0: if len(self.outputs) < self.max_outputs: self.outputs.remove(self.outputs.values()[-1]) # Direction vector self.add_output('NodeSocketFloat', 'X') # World X diff --git a/blender/arm/logicnode/math/LN_vector_math.py b/blender/arm/logicnode/math/LN_vector_math.py index 22f9eaa4..517babbf 100644 --- a/blender/arm/logicnode/math/LN_vector_math.py +++ b/blender/arm/logicnode/math/LN_vector_math.py @@ -42,7 +42,7 @@ class VectorMathNode(ArmLogicTreeNode): if (self.property0 == 'Dot Product'): self.add_output('NodeSocketFloat', 'Scalar') # Scalar - property1: BoolProperty(name='Separator Out', default=False, set=set_bool, get=get_bool) + property1: HaxeBoolProperty('property1', name='Separator Out', default=False, set=set_bool, get=get_bool) @staticmethod def get_enum_id_value(obj, prop_name, value): @@ -105,7 +105,8 @@ class VectorMathNode(ArmLogicTreeNode): self.add_output('NodeSocketFloat', 'Length') self['property0'] = value - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Add', 'Add', 'Add'), ('Dot Product', 'Dot Product', 'Dot Product'), ('Multiply', 'Multiply', 'Multiply'), diff --git a/blender/arm/logicnode/miscellaneous/LN_call_group.py b/blender/arm/logicnode/miscellaneous/LN_call_group.py index bf483e0d..cb5849bb 100644 --- a/blender/arm/logicnode/miscellaneous/LN_call_group.py +++ b/blender/arm/logicnode/miscellaneous/LN_call_group.py @@ -15,7 +15,7 @@ class CallGroupNode(ArmLogicTreeNode): def property0(self): return arm.utils.safesrc(bpy.data.worlds['Arm'].arm_project_package) + '.node.' + arm.utils.safesrc(self.property0_.name) - property0_: PointerProperty(name='Group', type=bpy.types.NodeTree) + property0_: HaxePointerProperty('property0', name='Group', type=bpy.types.NodeTree) def init(self, context): super(CallGroupNode, self).init(context) diff --git a/blender/arm/logicnode/miscellaneous/LN_set_debug_console_settings.py b/blender/arm/logicnode/miscellaneous/LN_set_debug_console_settings.py index a40602e8..24f8d485 100644 --- a/blender/arm/logicnode/miscellaneous/LN_set_debug_console_settings.py +++ b/blender/arm/logicnode/miscellaneous/LN_set_debug_console_settings.py @@ -6,7 +6,8 @@ class SetDebugConsoleSettings(ArmLogicTreeNode): bl_label = 'Set Debug Console Settings' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('left', 'Anchor Left', 'Anchor debug console in the top left'), ('center', 'Anchor Center', 'Anchor debug console in the top center'), ('right', 'Anchor Right', 'Anchor the debug console in the top right')], diff --git a/blender/arm/logicnode/native/LN_expression.py b/blender/arm/logicnode/native/LN_expression.py index e216e5b2..d2d7e853 100644 --- a/blender/arm/logicnode/native/LN_expression.py +++ b/blender/arm/logicnode/native/LN_expression.py @@ -9,7 +9,7 @@ class ExpressionNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'haxe' - property0: StringProperty(name='', default='') + property0: HaxeStringProperty('property0', name='', default='') def init(self, context): super(ExpressionNode, self).init(context) diff --git a/blender/arm/logicnode/native/LN_script.py b/blender/arm/logicnode/native/LN_script.py index d9cf4550..c308bfe6 100644 --- a/blender/arm/logicnode/native/LN_script.py +++ b/blender/arm/logicnode/native/LN_script.py @@ -15,7 +15,7 @@ class ScriptNode(ArmLogicTreeNode): return bpy.data.texts[self.property0_].as_string() if self.property0_ in bpy.data.texts else '' - property0_: StringProperty(name='Text', default='') + property0_: HaxeStringProperty('property0', name='Text', default='') def init(self, context): super(ScriptNode, self).init(context) diff --git a/blender/arm/logicnode/object/LN_get_object_child.py b/blender/arm/logicnode/object/LN_get_object_child.py index 40fcf310..be1b4de3 100644 --- a/blender/arm/logicnode/object/LN_get_object_child.py +++ b/blender/arm/logicnode/object/LN_get_object_child.py @@ -7,7 +7,8 @@ class GetChildNode(ArmLogicTreeNode): arm_section = 'relations' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('By Name', 'By Name', 'By Name'), ('Contains', 'Contains', 'Contains'), ('Starts With', 'Starts With', 'Starts With'), diff --git a/blender/arm/logicnode/object/LN_mesh.py b/blender/arm/logicnode/object/LN_mesh.py index 0d4b1f00..d68f2233 100644 --- a/blender/arm/logicnode/object/LN_mesh.py +++ b/blender/arm/logicnode/object/LN_mesh.py @@ -9,7 +9,7 @@ class MeshNode(ArmLogicTreeNode): bl_label = 'Mesh' arm_version = 1 - property0_get: PointerProperty(name='', type=bpy.types.Mesh) + property0_get: HaxePointerProperty('property0_get', name='', type=bpy.types.Mesh) def init(self, context): super(MeshNode, self).init(context) diff --git a/blender/arm/logicnode/object/LN_set_object_visible.py b/blender/arm/logicnode/object/LN_set_object_visible.py index 6fa99efb..76fe036f 100644 --- a/blender/arm/logicnode/object/LN_set_object_visible.py +++ b/blender/arm/logicnode/object/LN_set_object_visible.py @@ -9,7 +9,8 @@ class SetVisibleNode(ArmLogicTreeNode): arm_section = 'props' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('object', 'Object', 'All object componenets visibility'), ('mesh', 'Mesh', 'Mesh visibility only'), ('shadow', 'Shadow', 'Shadow visibility only'), diff --git a/blender/arm/logicnode/object/LN_spawn_object_by_name.py b/blender/arm/logicnode/object/LN_spawn_object_by_name.py index 92faa105..970f0bd7 100644 --- a/blender/arm/logicnode/object/LN_spawn_object_by_name.py +++ b/blender/arm/logicnode/object/LN_spawn_object_by_name.py @@ -6,7 +6,8 @@ class SpawnObjectByNameNode(ArmLogicTreeNode): bl_label = 'Spawn Object By Name' arm_version = 1 - property0: PointerProperty( + property0: HaxePointerProperty( + 'property0', type=bpy.types.Scene, name='Scene', description='The scene from which to take the object') diff --git a/blender/arm/logicnode/physics/LN_Add_rigid_body.py b/blender/arm/logicnode/physics/LN_Add_rigid_body.py index 27deaa24..165668d2 100644 --- a/blender/arm/logicnode/physics/LN_Add_rigid_body.py +++ b/blender/arm/logicnode/physics/LN_Add_rigid_body.py @@ -59,18 +59,16 @@ class AddRigidBodyNode(ArmLogicTreeNode): further down.""" self.update_sockets(context) - @property - def property1(self): - return 'true' if self.property1_ else 'false' - - property1_: BoolProperty( + property1: HaxeBoolProperty( + 'property1', name="Advanced", description="Show advanced options", default=False, update=update_advanced ) - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Box', 'Box', 'Box'), ('Sphere', 'Sphere', 'Sphere'), ('Capsule', 'Capsule', 'Capsule'), @@ -122,5 +120,5 @@ class AddRigidBodyNode(ArmLogicTreeNode): def draw_buttons(self, context, layout): - layout.prop(self, "property1_") - layout.prop(self, 'property0') \ No newline at end of file + layout.prop(self, "property1") + layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/physics/LN_add_physics_constraint.py b/blender/arm/logicnode/physics/LN_add_physics_constraint.py index 039a536f..d472d9b9 100644 --- a/blender/arm/logicnode/physics/LN_add_physics_constraint.py +++ b/blender/arm/logicnode/physics/LN_add_physics_constraint.py @@ -124,7 +124,8 @@ class AddPhysicsConstraintNode(ArmLogicTreeNode): self['property0'] = value - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Fixed', 'Fixed', 'Fixed'), ('Point', 'Point', 'Point'), ('Hinge', 'Hinge', 'Hinge'), diff --git a/blender/arm/logicnode/physics/LN_on_contact.py b/blender/arm/logicnode/physics/LN_on_contact.py index 7daa4e86..a96feb01 100644 --- a/blender/arm/logicnode/physics/LN_on_contact.py +++ b/blender/arm/logicnode/physics/LN_on_contact.py @@ -16,7 +16,8 @@ class OnContactNode(ArmLogicTreeNode): arm_section = 'contact' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('begin', 'Begin', 'The contact between the rigid bodies begins'), ('overlap', 'Overlap', 'The contact between the rigid bodies is happening'), ('end', 'End', 'The contact between the rigid bodies ends')], diff --git a/blender/arm/logicnode/physics/LN_on_contact_array.py b/blender/arm/logicnode/physics/LN_on_contact_array.py index 1a300c2d..9a35acf1 100644 --- a/blender/arm/logicnode/physics/LN_on_contact_array.py +++ b/blender/arm/logicnode/physics/LN_on_contact_array.py @@ -7,7 +7,8 @@ class OnContactArrayNode(ArmLogicTreeNode): arm_section = 'contact' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('begin', 'Begin', 'The contact between the rigid bodies begins'), ('overlap', 'Overlap', 'The contact between the rigid bodies is happening'), ('end', 'End', 'The contact between the rigid bodies ends')], diff --git a/blender/arm/logicnode/physics/LN_on_volume_trigger.py b/blender/arm/logicnode/physics/LN_on_volume_trigger.py index 225aa301..614a7e8f 100644 --- a/blender/arm/logicnode/physics/LN_on_volume_trigger.py +++ b/blender/arm/logicnode/physics/LN_on_volume_trigger.py @@ -10,7 +10,8 @@ class OnVolumeTriggerNode(ArmLogicTreeNode): bl_label = 'On Volume Trigger' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('begin', 'Begin', 'The contact between the rigid bodies begins'), ('overlap', 'Overlap', 'The contact between the rigid bodies is happening'), ('end', 'End', 'The contact between the rigid bodies ends')], diff --git a/blender/arm/logicnode/physics/LN_physics_constraint.py b/blender/arm/logicnode/physics/LN_physics_constraint.py index 457f24ca..fd9dd667 100644 --- a/blender/arm/logicnode/physics/LN_physics_constraint.py +++ b/blender/arm/logicnode/physics/LN_physics_constraint.py @@ -26,32 +26,21 @@ class PhysicsConstraintNode(ArmLogicTreeNode): def update_spring(self, context): self.update_sockets(context) - property0: EnumProperty( - items = [('Linear', 'Linear', 'Linear'), - ('Angular', 'Angular', 'Angular')], + property0: HaxeEnumProperty( + 'property0', + items=[('Linear', 'Linear', 'Linear'), + ('Angular', 'Angular', 'Angular')], name='Type', default='Linear') - - @property - def property1(self): - if(self.property1_ == 'X'): - return 'X' - if(self.property1_ == 'Y'): - return 'Y' - if(self.property1_ == 'Z'): - return 'Z' - property1_: EnumProperty( - items = [('X', 'X', 'X'), - ('Y', 'Y', 'Y'), - ('Z', 'Z', 'Z')], + property1: HaxeEnumProperty( + 'property1', + items=[('X', 'X', 'X'), + ('Y', 'Y', 'Y'), + ('Z', 'Z', 'Z')], name='Axis', default='X') - @property - def property2(self): - if(self.property2_): - return 'true' if self.property2_ else 'false' - - property2_: BoolProperty( + property2: HaxeBoolProperty( + 'property2', name="Spring", description="Is a spring constraint", default=False, @@ -66,14 +55,13 @@ class PhysicsConstraintNode(ArmLogicTreeNode): self.add_input('NodeSocketFloat', 'Lower limit') self.add_input('NodeSocketFloat', 'Upper limit') self.add_output('NodeSocketShader', 'Constraint') - - def update_sockets(self, context): - while (len(self.inputs) > 0): + def update_sockets(self, context): + while len(self.inputs) > 0: self.inputs.remove(self.inputs.values()[-1]) # Add dynamic input sockets - if self.property2_: + if self.property2: self.add_input('NodeSocketFloat', 'Stiffness', 10.0) self.add_input('NodeSocketFloat', 'Damping', 0.5) else: @@ -82,6 +70,5 @@ class PhysicsConstraintNode(ArmLogicTreeNode): def draw_buttons(self, context, layout): layout.prop(self, 'property0') - layout.prop(self, 'property1_') - layout.prop(self, 'property2_') - + layout.prop(self, 'property1') + layout.prop(self, 'property2') diff --git a/blender/arm/logicnode/physics/LN_set_rb_activation_state.py b/blender/arm/logicnode/physics/LN_set_rb_activation_state.py index 0d193707..120ead99 100644 --- a/blender/arm/logicnode/physics/LN_set_rb_activation_state.py +++ b/blender/arm/logicnode/physics/LN_set_rb_activation_state.py @@ -7,7 +7,8 @@ class SetActivationStateNode(ArmLogicTreeNode): bl_icon = 'NONE' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('inactive', 'Inactive', 'The rigid body simulation is deactivated'), ('active', 'Active', 'The rigid body simulation is activated'), ('always active', 'Always Active', 'The rigid body simulation is never deactivated'), diff --git a/blender/arm/logicnode/physics/LN_volume_trigger.py b/blender/arm/logicnode/physics/LN_volume_trigger.py index e9ba5bb1..240588aa 100644 --- a/blender/arm/logicnode/physics/LN_volume_trigger.py +++ b/blender/arm/logicnode/physics/LN_volume_trigger.py @@ -12,7 +12,8 @@ class VolumeTriggerNode(ArmLogicTreeNode): arm_section = 'misc' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('begin', 'Begin', 'The contact between the rigid bodies begins'), ('overlap', 'Overlap', 'The contact between the rigid bodies is happening'), ('end', 'End', 'The contact between the rigid bodies ends')], diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_set_global_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_set_global_node.py index 14f4e895..ee3aaad5 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_set_global_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_set_global_node.py @@ -32,7 +32,8 @@ class ColorgradingSetGlobalNode(ArmLogicTreeNode): arm_version = 1 # TODO: RRESET FILE OPTION FOR THE BELOW - property0 : EnumProperty( + property0 : HaxeEnumProperty( + 'property0', items = [('RGB', 'RGB', 'RGB'), ('Uniform', 'Uniform', 'Uniform')], name='Mode', default='Uniform', update=update_node) diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_set_highlight_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_set_highlight_node.py index 2f34d316..64144b18 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_set_highlight_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_set_highlight_node.py @@ -32,7 +32,8 @@ class ColorgradingSetHighlightNode(ArmLogicTreeNode): arm_version = 1 # TODO: RRESET FILE OPTION FOR THE BELOW - property0 : EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('RGB', 'RGB', 'RGB'), ('Uniform', 'Uniform', 'Uniform')], name='Mode', default='Uniform', update=update_node) diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_set_midtone_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_set_midtone_node.py index 5b1c31e6..0845c625 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_set_midtone_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_set_midtone_node.py @@ -32,7 +32,8 @@ class ColorgradingSetMidtoneNode(ArmLogicTreeNode): arm_version = 1 # TODO: RRESET FILE OPTION FOR THE BELOW - property0 : EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('RGB', 'RGB', 'RGB'), ('Uniform', 'Uniform', 'Uniform')], name='Mode', default='Uniform', update=update_node) diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_set_shadow_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_set_shadow_node.py index b014cff4..b32f64e7 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_set_shadow_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_set_shadow_node.py @@ -32,7 +32,8 @@ class ColorgradingSetShadowNode(ArmLogicTreeNode): arm_version = 1 # TODO: RRESET FILE OPTION FOR THE BELOW - property0 : EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('RGB', 'RGB', 'RGB'), ('Uniform', 'Uniform', 'Uniform')], name='Mode', default='Uniform', update=update_node) diff --git a/blender/arm/logicnode/renderpath/LN_set_msaa_quality.py b/blender/arm/logicnode/renderpath/LN_set_msaa_quality.py index 52022aa4..0578c2d8 100644 --- a/blender/arm/logicnode/renderpath/LN_set_msaa_quality.py +++ b/blender/arm/logicnode/renderpath/LN_set_msaa_quality.py @@ -5,7 +5,8 @@ class RpMSAANode(ArmLogicTreeNode): bl_idname = 'LNRpMSAANode' bl_label = 'Set MSAA Quality' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('1', '1', '1'), ('2', '2', '2'), ('4', '4', '4'), diff --git a/blender/arm/logicnode/renderpath/LN_set_post_process_quality.py b/blender/arm/logicnode/renderpath/LN_set_post_process_quality.py index a93d3f9e..e7b6e31e 100644 --- a/blender/arm/logicnode/renderpath/LN_set_post_process_quality.py +++ b/blender/arm/logicnode/renderpath/LN_set_post_process_quality.py @@ -5,7 +5,8 @@ class RpConfigNode(ArmLogicTreeNode): bl_idname = 'LNRpConfigNode' bl_label = 'Set Post Process Quality' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('SSGI', 'SSGI', 'SSGI'), ('SSR', 'SSR', 'SSR'), ('Bloom', 'Bloom', 'Bloom'), diff --git a/blender/arm/logicnode/renderpath/LN_set_shader_uniform.py b/blender/arm/logicnode/renderpath/LN_set_shader_uniform.py index 676e6994..cf34577e 100644 --- a/blender/arm/logicnode/renderpath/LN_set_shader_uniform.py +++ b/blender/arm/logicnode/renderpath/LN_set_shader_uniform.py @@ -21,7 +21,8 @@ class SetShaderUniformNode(ArmLogicTreeNode): elif self.property0 in ('vec2', 'vec3', 'vec4'): self.add_input('NodeSocketVector', 'Vector') - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('int', 'int', 'int'), ('float', 'float', 'float'), ('vec2', 'vec2', 'vec2'), diff --git a/blender/arm/logicnode/renderpath/LN_set_shadows_quality.py b/blender/arm/logicnode/renderpath/LN_set_shadows_quality.py index ba42ac3a..0d26e950 100644 --- a/blender/arm/logicnode/renderpath/LN_set_shadows_quality.py +++ b/blender/arm/logicnode/renderpath/LN_set_shadows_quality.py @@ -5,7 +5,8 @@ class RpShadowQualityNode(ArmLogicTreeNode): bl_idname = 'LNRpShadowQualityNode' bl_label = 'Set Shadows Quality' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('High', 'High', 'High'), ('Medium', 'Medium', 'Medium'), ('Low', 'Low', 'Low') diff --git a/blender/arm/logicnode/renderpath/LN_set_ssaa_quality.py b/blender/arm/logicnode/renderpath/LN_set_ssaa_quality.py index f102f21c..375b76e2 100644 --- a/blender/arm/logicnode/renderpath/LN_set_ssaa_quality.py +++ b/blender/arm/logicnode/renderpath/LN_set_ssaa_quality.py @@ -5,7 +5,8 @@ class RpSuperSampleNode(ArmLogicTreeNode): bl_idname = 'LNRpSuperSampleNode' bl_label = 'Set SSAA Quality' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('1', '1', '1'), ('1.5', '1.5', '1.5'), ('2', '2', '2'), diff --git a/blender/arm/logicnode/scene/LN_collection.py b/blender/arm/logicnode/scene/LN_collection.py index 54442f2f..ce4e952c 100644 --- a/blender/arm/logicnode/scene/LN_collection.py +++ b/blender/arm/logicnode/scene/LN_collection.py @@ -12,7 +12,7 @@ class GroupNode(ArmLogicTreeNode): arm_section = 'collection' arm_version = 1 - property0: PointerProperty(name='', type=bpy.types.Collection) + property0: HaxePointerProperty('property0', name='', type=bpy.types.Collection) def init(self, context): super(GroupNode, self).init(context) diff --git a/blender/arm/logicnode/scene/LN_scene.py b/blender/arm/logicnode/scene/LN_scene.py index 29f68205..4131608a 100644 --- a/blender/arm/logicnode/scene/LN_scene.py +++ b/blender/arm/logicnode/scene/LN_scene.py @@ -9,7 +9,7 @@ class SceneNode(ArmLogicTreeNode): bl_label = 'Scene' arm_version = 1 - property0_get: PointerProperty(name='', type=bpy.types.Scene) + property0_get: HaxePointerProperty('property0_get', name='', type=bpy.types.Scene) def init(self, context): super(SceneNode, self).init(context) diff --git a/blender/arm/logicnode/scene/LN_spawn_collection.py b/blender/arm/logicnode/scene/LN_spawn_collection.py index b8d6caaf..12e995e9 100644 --- a/blender/arm/logicnode/scene/LN_spawn_collection.py +++ b/blender/arm/logicnode/scene/LN_spawn_collection.py @@ -28,7 +28,7 @@ class SpawnCollectionNode(ArmLogicTreeNode): arm_section = 'collection' arm_version = 1 - property0: PointerProperty(name='Collection', type=bpy.types.Collection) + property0: HaxePointerProperty('property0', name='Collection', type=bpy.types.Collection) def init(self, context): super(SpawnCollectionNode, self).init(context) diff --git a/blender/arm/logicnode/sound/LN_play_sound.py b/blender/arm/logicnode/sound/LN_play_sound.py index f87838ea..ac72729d 100644 --- a/blender/arm/logicnode/sound/LN_play_sound.py +++ b/blender/arm/logicnode/sound/LN_play_sound.py @@ -31,25 +31,30 @@ class PlaySoundNode(ArmLogicTreeNode): bl_width_default = 200 arm_version = 1 - property0: PointerProperty(name='', type=bpy.types.Sound) - property1: BoolProperty( + property0: HaxePointerProperty('property0', name='', type=bpy.types.Sound) + property1: HaxeBoolProperty( + 'property1', name='Loop', description='Play the sound in a loop', default=False) - property2: BoolProperty( + property2: HaxeBoolProperty( + 'property2', name='Retrigger', description='Play the sound from the beginning every time', default=False) - property3: BoolProperty( + property3: HaxeBoolProperty( + 'property3', name='Use Custom Sample Rate', description='If enabled, override the default sample rate', default=False) - property4: IntProperty( + property4: HaxeIntProperty( + 'property4', name='Sample Rate', description='Set the sample rate used to play this sound', default=44100, min=0) - property5: BoolProperty( + property5: HaxeBoolProperty( + 'property5', name='Stream', description='Stream the sound from disk', default=False diff --git a/blender/arm/logicnode/string/LN_string_case.py b/blender/arm/logicnode/string/LN_string_case.py index 30f8f276..64d35ae1 100644 --- a/blender/arm/logicnode/string/LN_string_case.py +++ b/blender/arm/logicnode/string/LN_string_case.py @@ -5,7 +5,8 @@ class CaseStringNode(ArmLogicTreeNode): bl_idname = 'LNCaseStringNode' bl_label = 'String Case' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Upper Case', 'Upper Case', 'Upper Case'), ('Lower Case', 'Lower Case', 'Lower Case'), ], diff --git a/blender/arm/logicnode/string/LN_string_contains.py b/blender/arm/logicnode/string/LN_string_contains.py index 1f99961a..dafe546f 100644 --- a/blender/arm/logicnode/string/LN_string_contains.py +++ b/blender/arm/logicnode/string/LN_string_contains.py @@ -5,7 +5,8 @@ class ContainsStringNode(ArmLogicTreeNode): bl_idname = 'LNContainsStringNode' bl_label = 'String Contains' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Contains', 'Contains', 'Contains'), ('Starts With', 'Starts With', 'Starts With'), ('Ends With', 'Ends With', 'Ends With'), diff --git a/blender/arm/logicnode/trait/LN_trait.py b/blender/arm/logicnode/trait/LN_trait.py index bcec41c0..b572923f 100644 --- a/blender/arm/logicnode/trait/LN_trait.py +++ b/blender/arm/logicnode/trait/LN_trait.py @@ -8,7 +8,7 @@ class TraitNode(ArmLogicTreeNode): bl_label = 'Trait' arm_version = 1 - property0: StringProperty(name='', default='') + property0: HaxeStringProperty('property0', name='', default='') def init(self, context): super(TraitNode, self).init(context) diff --git a/blender/arm/logicnode/transform/LN_get_world_orientation.py b/blender/arm/logicnode/transform/LN_get_world_orientation.py index 3ba71b5b..7efce5b3 100644 --- a/blender/arm/logicnode/transform/LN_get_world_orientation.py +++ b/blender/arm/logicnode/transform/LN_get_world_orientation.py @@ -7,7 +7,8 @@ class GetWorldNode(ArmLogicTreeNode): arm_section = 'rotation' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Right', 'Right', 'The object right (X) direction'), ('Look', 'Look', 'The object look (Y) direction'), ('Up', 'Up', 'The object up (Z) direction')], diff --git a/blender/arm/logicnode/transform/LN_look_at.py b/blender/arm/logicnode/transform/LN_look_at.py index b1bdc31a..0d276883 100644 --- a/blender/arm/logicnode/transform/LN_look_at.py +++ b/blender/arm/logicnode/transform/LN_look_at.py @@ -7,7 +7,8 @@ class LookAtNode(ArmLogicTreeNode): arm_section = 'rotation' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('X', ' X', 'X'), ('-X', '-X', '-X'), ('Y', ' Y', 'Y'), diff --git a/blender/arm/logicnode/transform/LN_rotate_object.py b/blender/arm/logicnode/transform/LN_rotate_object.py index 7b21abb4..29f2c39e 100644 --- a/blender/arm/logicnode/transform/LN_rotate_object.py +++ b/blender/arm/logicnode/transform/LN_rotate_object.py @@ -40,10 +40,11 @@ class RotateObjectNode(ArmLogicTreeNode): else: layout.prop(self, 'property0') - property0: EnumProperty( - items = [('Euler Angles', 'Euler Angles', 'Euler Angles'), - ('Angle Axies (Radians)', 'Angle Axies (Radians)', 'Angle Axies (Radians)'), - ('Angle Axies (Degrees)', 'Angle Axies (Degrees)', 'Angle Axies (Degrees)'), - ('Quaternion', 'Quaternion', 'Quaternion')], + property0: HaxeEnumProperty( + 'property0', + items=[('Euler Angles', 'Euler Angles', 'Euler Angles'), + ('Angle Axies (Radians)', 'Angle Axies (Radians)', 'Angle Axies (Radians)'), + ('Angle Axies (Degrees)', 'Angle Axies (Degrees)', 'Angle Axies (Degrees)'), + ('Quaternion', 'Quaternion', 'Quaternion')], name='', default='Euler Angles', - update = on_property_update) + update=on_property_update) diff --git a/blender/arm/logicnode/transform/LN_set_object_rotation.py b/blender/arm/logicnode/transform/LN_set_object_rotation.py index 4a59e161..b9c38a45 100644 --- a/blender/arm/logicnode/transform/LN_set_object_rotation.py +++ b/blender/arm/logicnode/transform/LN_set_object_rotation.py @@ -33,7 +33,8 @@ class SetRotationNode(ArmLogicTreeNode): def draw_buttons(self, context, layout): layout.prop(self, 'property0') - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Euler Angles', 'Euler Angles', 'Euler Angles'), ('Angle Axies (Radians)', 'Angle Axies (Radians)', 'Angle Axies (Radians)'), ('Angle Axies (Degrees)', 'Angle Axies (Degrees)', 'Angle Axies (Degrees)'), diff --git a/blender/arm/logicnode/transform/LN_transform_to_vector.py b/blender/arm/logicnode/transform/LN_transform_to_vector.py index bb70185c..9fbc6c3d 100644 --- a/blender/arm/logicnode/transform/LN_transform_to_vector.py +++ b/blender/arm/logicnode/transform/LN_transform_to_vector.py @@ -31,7 +31,8 @@ class VectorFromTransformNode(ArmLogicTreeNode): def draw_buttons(self, context, layout): layout.prop(self, 'property0') - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Right', 'Right', 'The transform right (X) direction'), ('Look', 'Look', 'The transform look (Y) direction'), ('Up', 'Up', 'The transform up (Z) direction'), diff --git a/blender/arm/make_logic.py b/blender/arm/make_logic.py index 289bb6b0..eef667fe 100755 --- a/blender/arm/make_logic.py +++ b/blender/arm/make_logic.py @@ -158,26 +158,9 @@ def build_node(node: bpy.types.Node, f: TextIO) -> Optional[str]: f.write(f'\t\tthis.nodes["{name[1:]}"] = {name};\n') # Properties - for i in range(0, 10): - prop_name = 'property' + str(i) + '_get' - prop_found = hasattr(node, prop_name) - if not prop_found: - prop_name = 'property' + str(i) - prop_found = hasattr(node, prop_name) - if prop_found: - prop = getattr(node, prop_name) - if isinstance(prop, str): - prop = '"' + str(prop) + '"' - elif isinstance(prop, bool): - prop = str(prop).lower() - elif hasattr(prop, 'name'): # PointerProperty - prop = '"' + str(prop.name) + '"' - else: - if prop is None: - prop = 'null' - else: - prop = str(prop) - f.write('\t\t' + name + '.property' + str(i) + ' = ' + prop + ';\n') + for prop_name in arm.node_utils.get_haxe_property_names(node): + prop = arm.node_utils.haxe_format_prop(node, prop_name) + f.write('\t\t' + name + '.' + prop_name + ' = ' + prop + ';\n') # Create inputs for inp in node.inputs: @@ -284,23 +267,16 @@ def build_default_node(inp: bpy.types.NodeSocket): if is_custom_socket: # ArmCustomSockets need to implement get_default_value() - default_value = inp.get_default_value() - if isinstance(default_value, str): - default_value = '"{:s}"'.format(default_value.replace('"', '\\"') ) + default_value = arm.node_utils.haxe_format_socket_val(inp.get_default_value()) inp_type = inp.arm_socket_type # any custom socket's `type` is "VALUE". might as well have valuable type information for custom nodes as well. else: if hasattr(inp, 'default_value'): default_value = inp.default_value else: default_value = None - if isinstance(default_value, str): - default_value = '"{:s}"'.format(default_value.replace('"', '\\"') ) + default_value = arm.node_utils.haxe_format_socket_val(default_value) inp_type = inp.type - # Don't write 'None' into the Haxe code - if default_value is None: - default_value = 'null' - if inp_type == 'VECTOR': return f'new armory.logicnode.VectorNode(this, {default_value[0]}, {default_value[1]}, {default_value[2]})' elif inp_type == 'RGBA': diff --git a/blender/arm/node_utils.py b/blender/arm/node_utils.py index 5cee6346..e501707f 100755 --- a/blender/arm/node_utils.py +++ b/blender/arm/node_utils.py @@ -1,4 +1,4 @@ -from typing import Type, Union +from typing import Any, Generator, Type, Union import bpy from bpy.types import NodeSocket, NodeInputs, NodeOutputs @@ -83,6 +83,50 @@ def get_export_node_name(node: bpy.types.Node) -> str: return '_' + arm.utils.safesrc(node.name) +def get_haxe_property_names(node: bpy.types.Node) -> Generator[str, None, None]: + """Generator that yields the names of all node properties that have + a counterpart in the node's Haxe class. + """ + for i in range(0, 10): + prop_name = f'property{i}_get' + prop_found = hasattr(node, prop_name) + if not prop_found: + prop_name = f'property{i}' + prop_found = hasattr(node, prop_name) + if prop_found: + yield prop_name + + +def haxe_format_socket_val(socket_val: Any) -> str: + """Formats a socket value to be valid Haxe syntax.""" + if isinstance(socket_val, str): + socket_val = '"{:s}"'.format(socket_val.replace('"', '\\"')) + + elif socket_val is None: + # Don't write 'None' into the Haxe code + socket_val = 'null' + + return socket_val + + +def haxe_format_prop(node: bpy.types.Node, prop_name: str) -> str: + """Formats a property value to be valid Haxe syntax.""" + prop_value = getattr(node, prop_name) + if isinstance(prop_value, str): + prop_value = '"' + str(prop_value) + '"' + elif isinstance(prop_value, bool): + prop_value = str(prop_value).lower() + elif hasattr(prop_value, 'name'): # PointerProperty + prop_value = '"' + str(prop_value.name) + '"' + else: + if prop_value is None: + prop_value = 'null' + else: + prop_value = str(prop_value) + + return prop_value + + def nodetype_to_nodeitem(node_type: Type[bpy.types.Node]) -> NodeItem: """Create a NodeItem from a given node class.""" # Internal node types seem to have no bl_idname attribute From b2153dbcd2008e3656619f688d46eb973af6920b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 3 Jul 2021 19:45:35 +0200 Subject: [PATCH 196/264] Cleanup --- .../arm/logicnode/math/LN_math_expression.py | 22 +++++++++---------- .../arm/logicnode/math/LN_quaternion_math.py | 6 ++--- .../LN_set_debug_console_settings.py | 8 +++---- .../logicnode/physics/LN_Add_rigid_body.py | 18 +++++++-------- .../physics/LN_add_physics_constraint.py | 18 +++++++-------- .../physics/LN_physics_constraint.py | 3 ++- .../logicnode/transform/LN_rotate_object.py | 2 +- 7 files changed, 39 insertions(+), 38 deletions(-) diff --git a/blender/arm/logicnode/math/LN_math_expression.py b/blender/arm/logicnode/math/LN_math_expression.py index 923b6aee..125d3e77 100644 --- a/blender/arm/logicnode/math/LN_math_expression.py +++ b/blender/arm/logicnode/math/LN_math_expression.py @@ -8,15 +8,15 @@ class MathExpressionNode(ArmLogicTreeNode): arm_version = 1 min_inputs = 2 max_inputs = 10 - + @staticmethod def get_variable_name(index): return { - 0: 'a', - 1: 'b', - 2: 'c', - 3: 'd', - 4: 'e', + 0: 'a', + 1: 'b', + 2: 'c', + 3: 'd', + 4: 'e', 5: 'x', 6: 'y', 7: 'h', @@ -27,7 +27,7 @@ class MathExpressionNode(ArmLogicTreeNode): @staticmethod def get_clear_exp(value): return re.sub(r'[\-\+\*\/\(\)\^\%abcdexyhik0123456789. ]', '', value).strip() - + @staticmethod def get_invalid_characters(value): value = value.replace(' ', '') @@ -70,7 +70,7 @@ class MathExpressionNode(ArmLogicTreeNode): return False return True - @staticmethod + @staticmethod def matches(line, opendelim='(', closedelim=')'): stack = [] for m in re.finditer(r'[{}{}]'.format(opendelim, closedelim), line): @@ -92,7 +92,7 @@ class MathExpressionNode(ArmLogicTreeNode): if len(stack) > 0: for pos in stack: yield (False, 0, 0, 0) - + @staticmethod def isPartCorrect(s): if len(s.replace('p', '').replace(' ', '').split()) == 0: @@ -151,7 +151,7 @@ class MathExpressionNode(ArmLogicTreeNode): elif not self.isCorrect(self, value.replace(' ', '')): val_error = True self.set_exp_error(val_error) - + def get_exp(self): return self.get('property0', 'a + b') @@ -187,4 +187,4 @@ class MathExpressionNode(ArmLogicTreeNode): op = column.operator('arm.node_remove_input', text='', icon='X', emboss=True) op.node_index = str(id(self)) if len(self.inputs) == 2: - column.enabled = False \ No newline at end of file + column.enabled = False diff --git a/blender/arm/logicnode/math/LN_quaternion_math.py b/blender/arm/logicnode/math/LN_quaternion_math.py index 093b8c83..bcaa7753 100644 --- a/blender/arm/logicnode/math/LN_quaternion_math.py +++ b/blender/arm/logicnode/math/LN_quaternion_math.py @@ -84,7 +84,7 @@ class QuaternionMathNode(ArmLogicTreeNode): self.inputs.remove(self.inputs.values()[-1]) if (select_prev == 'DotProduct') or (select_prev == 'ToAxisAngle') or (select_prev == 'Module'): self.outputs.remove(self.outputs.values()[-1]) - + # Many arguments: Add, Subtract, DotProduct, Multiply, MultiplyFloat if (self.get_count_in(select_current) == 0): if (select_current == "MultiplyFloats"): @@ -95,7 +95,7 @@ class QuaternionMathNode(ArmLogicTreeNode): self.add_input('NodeSocketVector', 'Quaternion ' + str(len(self.inputs))) if (select_current == 'DotProduct'): self.add_output('NodeSocketFloat', 'Scalar') - + # 3 arguments: Lerp, Slerp, FromAxisAngle, FromEuler if (self.get_count_in(select_current) == 3): if (select_current == 'Lerp') or (select_current == 'Slerp'): @@ -111,7 +111,7 @@ class QuaternionMathNode(ArmLogicTreeNode): self.add_input('NodeSocketFloat', 'X') self.add_input('NodeSocketFloat', 'Y') self.add_input('NodeSocketFloat', 'Z') - + # 2 arguments: FromTo, FromMat, FromRotationMat, ToAxisAngle if (self.get_count_in(select_current) == 2): if (select_current == 'FromTo'): diff --git a/blender/arm/logicnode/miscellaneous/LN_set_debug_console_settings.py b/blender/arm/logicnode/miscellaneous/LN_set_debug_console_settings.py index 24f8d485..2a02a150 100644 --- a/blender/arm/logicnode/miscellaneous/LN_set_debug_console_settings.py +++ b/blender/arm/logicnode/miscellaneous/LN_set_debug_console_settings.py @@ -13,11 +13,11 @@ class SetDebugConsoleSettings(ArmLogicTreeNode): ('right', 'Anchor Right', 'Anchor the debug console in the top right')], name='', default='right') - def init(self, context): - super(SetDebugConsoleSettings, self).init(context) + def init(self, context): + super(SetDebugConsoleSettings, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketBool', 'Visible') - self.add_input('NodeSocketFloat', 'Scale') + self.add_input('NodeSocketBool', 'Visible') + self.add_input('NodeSocketFloat', 'Scale') self.inputs[-1].default_value = 1.0 self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/physics/LN_Add_rigid_body.py b/blender/arm/logicnode/physics/LN_Add_rigid_body.py index 165668d2..13fbea61 100644 --- a/blender/arm/logicnode/physics/LN_Add_rigid_body.py +++ b/blender/arm/logicnode/physics/LN_Add_rigid_body.py @@ -1,12 +1,12 @@ from arm.logicnode.arm_nodes import * class AddRigidBodyNode(ArmLogicTreeNode): - """Adds a rigid body to an object if not already present. + """Adds a rigid body to an object if not already present. @option Advanced: Shows optional advanced options for rigid body. @option Shape: Shape of the rigid body including Box, Sphere, Capsule, Cone, Cylinder, Convex Hull and Mesh - + @input Object: Object to which rigid body is added. @input Mass: Mass of the rigid body. Must be > 0. @@ -14,9 +14,9 @@ class AddRigidBodyNode(ArmLogicTreeNode): @input Active: Rigid body actively participates in the physics world and will be affected by collisions @input Animated: Rigid body follows animation and will affect other active non-animated rigid bodies. - - @input Trigger: Rigid body behaves as a trigger and detects collision. However, rigd body does not contribute to or receive collissions. - + + @input Trigger: Rigid body behaves as a trigger and detects collision. However, rigd body does not contribute to or receive collissions. + @input Friction: Surface friction of the rigid body. Minimum value = 0, Preferred max value = 1. @input Bounciness: How elastic is the surface of the rigid body. Minimum value = 0, Preferred max value = 1. @@ -34,7 +34,7 @@ class AddRigidBodyNode(ArmLogicTreeNode): @input Use Deactivation: Deactive this rigid body when below the Linear and Angular velocity threshold. Enable to improve performance. @input Linear Velocity Threshold: Velocity below which decativation occurs if enabled. - + @input Angular Velocity Threshold: Velocity below which decativation occurs if enabled. @input Collision Group: A set of rigid bodies that can interact with each other @@ -43,9 +43,9 @@ class AddRigidBodyNode(ArmLogicTreeNode): @output Rigid body: Object to which rigid body was added. - @output Out: activated after rigid body is added. + @output Out: activated after rigid body is added. """ - + bl_idname = 'LNAddRigidBodyNode' bl_label = 'Add Rigid Body' arm_version = 1 @@ -80,7 +80,7 @@ class AddRigidBodyNode(ArmLogicTreeNode): def init(self, context): super(AddRigidBodyNode, self).init(context) - + self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('NodeSocketFloat', 'Mass', 1.0) diff --git a/blender/arm/logicnode/physics/LN_add_physics_constraint.py b/blender/arm/logicnode/physics/LN_add_physics_constraint.py index d472d9b9..f8537fa9 100644 --- a/blender/arm/logicnode/physics/LN_add_physics_constraint.py +++ b/blender/arm/logicnode/physics/LN_add_physics_constraint.py @@ -3,11 +3,11 @@ from arm.logicnode.arm_nodes import * class AddPhysicsConstraintNode(ArmLogicTreeNode): """ Add a physics constraint to constrain two rigid bodies if not already present. - + @option Fixed: No fredom of movement. Relative positions and rotations of rigid bodies are fixed @option Point: Both rigid bodies are constrained at the pivot object. - + @option Hinge: Constrained objects can move only along angular Z axis of the pivot object. @option Slider: Constrained objects can move only along linear X axis of the pivot object. @@ -57,15 +57,15 @@ class AddPhysicsConstraintNode(ArmLogicTreeNode): @staticmethod def get_count_in(type_name): return { - 'Fixed': 0, - 'Point': 1, - 'Hinge': 2, + 'Fixed': 0, + 'Point': 1, + 'Hinge': 2, 'Slider': 3, - 'Piston': 4, + 'Piston': 4, 'Generic Spring': 5 }.get(type_name, 0) - def get_enum(self): + def get_enum(self): return self.get('property0', 0) def set_enum(self, value): @@ -133,7 +133,7 @@ class AddPhysicsConstraintNode(ArmLogicTreeNode): ('Piston', 'Piston', 'Piston'), ('Generic Spring', 'Generic Spring', 'Generic Spring')], name='Type', default='Fixed', set=set_enum, get=get_enum) - + def __init__(self): array_nodes[str(id(self))] = self @@ -150,7 +150,7 @@ class AddPhysicsConstraintNode(ArmLogicTreeNode): def draw_buttons(self, context, layout): layout.prop(self, 'property0') - + #GenericSpring: if (self.get_count_in(self.property0) == 5): grid0 = layout.grid_flow(row_major=True, columns=1, align=True) diff --git a/blender/arm/logicnode/physics/LN_physics_constraint.py b/blender/arm/logicnode/physics/LN_physics_constraint.py index fd9dd667..28dd0f6f 100644 --- a/blender/arm/logicnode/physics/LN_physics_constraint.py +++ b/blender/arm/logicnode/physics/LN_physics_constraint.py @@ -1,5 +1,6 @@ from arm.logicnode.arm_nodes import * + class PhysicsConstraintNode(ArmLogicTreeNode): """ Custom physics constraint to add to `Add Physics Constarint` node. @@ -46,7 +47,7 @@ class PhysicsConstraintNode(ArmLogicTreeNode): default=False, update=update_spring ) - + def __init__(self): array_nodes[str(id(self))] = self diff --git a/blender/arm/logicnode/transform/LN_rotate_object.py b/blender/arm/logicnode/transform/LN_rotate_object.py index 29f2c39e..66cc6498 100644 --- a/blender/arm/logicnode/transform/LN_rotate_object.py +++ b/blender/arm/logicnode/transform/LN_rotate_object.py @@ -28,7 +28,7 @@ class RotateObjectNode(ArmLogicTreeNode): self.inputs[2].name = "Axis" self.inputs[3].name = "Angle" else: - raise ValueError('No nodesocket labels for current input mode: check self-consistancy of action_set_rotation.py') + raise ValueError('No nodesocket labels for current input mode: check self-consistancy of LN_rotate_object.py') def draw_buttons(self, context, layout): # this block is here to ensure backwards compatibility and warn the user. From 4e19ddfeb0c2678eb590f3f6f8d67e671206c865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 3 Jul 2021 19:47:09 +0200 Subject: [PATCH 197/264] Do not send live patch events if live patch isn't running --- blender/arm/live_patch.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index ce9f8f50..c9a89c9e 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -19,6 +19,9 @@ patch_id = 0 # Any object can act as a message bus owner msgbus_owner = object() +# Whether live patch is currently active +__running = False + def start(): """Start the live patch session.""" @@ -34,9 +37,15 @@ def start(): listen(light_type, "color", "light_color") listen(light_type, "energy", "light_energy") + global __running + __running = True + def stop(): """Stop the live patch session.""" + global __running + __running = False + log.debug("Live patch session stopped") bpy.msgbus.clear_by_owner(msgbus_owner) @@ -103,6 +112,9 @@ def listen(rna_type: Type[bpy.types.bpy_struct], prop: str, event_id: str): def send_event(event_id: str, opt_data: Any = None): """Send the result of the given event to Krom.""" + if not __running: + return + if hasattr(bpy.context, 'object') and bpy.context.object is not None: obj = bpy.context.object.name @@ -186,6 +198,8 @@ def on_operator(operator_id: str): (*) https://developer.blender.org/T72109 """ + if not __running: + return # Don't re-export the scene for the following operators if operator_id in ("VIEW3D_OT_select", "OUTLINER_OT_item_activate", "OBJECT_OT_editmode_toggle", "NODE_OT_select", "NODE_OT_translate_attach_remove_on_cancel"): return From cb800729d2a8947397b52aa302736af7623d0db1 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Sat, 3 Jul 2021 20:12:10 +0200 Subject: [PATCH 198/264] Add check Add a check to see if input socket is linked to correct output socket --- Sources/armory/logicnode/LogicNode.hx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Sources/armory/logicnode/LogicNode.hx b/Sources/armory/logicnode/LogicNode.hx index 9e5e367c..b7b78b1d 100644 --- a/Sources/armory/logicnode/LogicNode.hx +++ b/Sources/armory/logicnode/LogicNode.hx @@ -38,12 +38,16 @@ class LogicNode { **/ function runOutput(i: Int) { if (i >= outputs.length) return; - for (o in outputs[i]) { + for (output in outputs[i]) { // Check which input activated the node - for (j in 0...o.inputs.length) { - if (o.inputs[j].node == this) { - o.run(j); - break; + for (j in 0...output.inputs.length) { + // Check if the node is connected to the current node + if (output.inputs[j].node == this) { + // Check if the input socekt is linked to current output socket + if (output.inputs[j].from == i) { + output.run(j); + break; + } } } } @@ -60,6 +64,7 @@ class LogicNodeInput { @:allow(armory.logicnode.LogicNode) var node: LogicNode; + @:allow(armory.logicnode.LogicNode) var from: Int; // Socket index public function new(node: LogicNode, from: Int) { From a6ec652d5f3ba14d46b07665365e027b15d4cb56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 3 Jul 2021 22:49:19 +0200 Subject: [PATCH 199/264] Fix identity node replacement for Blender 2.93 --- blender/arm/logicnode/replacement.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/blender/arm/logicnode/replacement.py b/blender/arm/logicnode/replacement.py index d062a7bc..99585f13 100644 --- a/blender/arm/logicnode/replacement.py +++ b/blender/arm/logicnode/replacement.py @@ -61,28 +61,27 @@ class NodeReplacement: """ in_socks = {i: i for i in range(len(node.inputs))} out_socks = {i: i for i in range(len(node.outputs))} - props = {} - i = 0 - # finding all the properties fo a node is not possible in a clean way for now. - # so, I'll assume their names start with "property", and list all the node's attributes that fulfill that condition. - # next, to check that those are indeed properties (in the blender sense), we need to check the class's type annotations. - # those annotations are not even instances of bpy.types.Property, but tuples, with the first element being a function accessible at bpy.props.XXXProperty - property_types = [] - for possible_prop_type in dir(bpy.props): - if possible_prop_type.endswith('Property'): - property_types.append(getattr(bpy.props, possible_prop_type)) + # Find all properties for this node + props = {} possible_properties = [] for attrname in dir(node): + # We assume that property names start with 'property' if attrname.startswith('property'): possible_properties.append(attrname) + for attrname in possible_properties: + # Search in type annotations if attrname not in node.__annotations__: continue - if not isinstance(node.__annotations__[attrname], tuple): + + # Properties must be annotated with '_PropertyDeferred', see + # https://developer.blender.org/rB37e6a1995ac7eeabd5b6a56621ad5a850dae4149 + # and https://developer.blender.org/rBc44c611c6d8c6ae071b48efb5fc07168f18cd17e + if not isinstance(node.__annotations__[attrname], bpy.props._PropertyDeferred): continue - if node.__annotations__[attrname][0] in property_types: - props[attrname] = attrname + + props[attrname] = attrname return NodeReplacement( node.bl_idname, node.arm_version, node.bl_idname, type(node).arm_version, From e7da337530932b36cd7c9ef49567ae9df87c9a76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Mon, 5 Jul 2021 19:25:47 +0200 Subject: [PATCH 200/264] UI Canvas: use font from asset for all element types --- Sources/armory/ui/Canvas.hx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Sources/armory/ui/Canvas.hx b/Sources/armory/ui/Canvas.hx index 2c66cf68..dc798adc 100644 --- a/Sources/armory/ui/Canvas.hx +++ b/Sources/armory/ui/Canvas.hx @@ -58,18 +58,18 @@ class Canvas { var rotated = element.rotation != null && element.rotation != 0; if (rotated) ui.g.pushRotation(element.rotation, ui._x + scaled(element.width) / 2, ui._y + scaled(element.height) / 2); + var font = ui.ops.font; + var fontAsset = isFontAsset(element.asset); + if (fontAsset) ui.ops.font = getAsset(canvas, element.asset); + switch (element.type) { case Text: - var font = ui.ops.font; var size = ui.fontSize; - var fontAsset = element.asset != null && StringTools.endsWith(element.asset, ".ttf"); - if (fontAsset) ui.ops.font = getAsset(canvas, element.asset); ui.fontSize = scaled(element.height); ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); ui.text(getText(canvas, element), element.alignment); - ui.ops.font = font; ui.fontSize = size; case Button: @@ -90,7 +90,6 @@ class Canvas { case Image: var image = getAsset(canvas, element.asset); - var fontAsset = element.asset != null && StringTools.endsWith(element.asset, ".ttf"); if (image != null && !fontAsset) { ui.imageScrollAlign = false; var tint = element.color != null ? element.color : 0xffffffff; @@ -218,6 +217,8 @@ class Canvas { case Empty: } + ui.ops.font = font; + if (element.children != null) { for (id in element.children) { drawElement(ui, canvas, elemById(canvas, id), scaled(element.x) + px, scaled(element.y) + py); @@ -257,6 +258,10 @@ class Canvas { return Std.int(f * _ui.SCALE()); } + static inline function isFontAsset(assetName: Null): Bool { + return assetName != null && StringTools.endsWith(assetName.toLowerCase(), ".ttf"); + } + public static inline function getColor(color: Null, defaultColor: Int): Int { return color != null ? color : defaultColor; } From dc6753c2ca3bda21fd1a4f16972e6a7ed5d03e08 Mon Sep 17 00:00:00 2001 From: Lubos Lenco Date: Tue, 6 Jul 2021 10:46:44 +0200 Subject: [PATCH 201/264] Bump version --- blender/arm/props.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/props.py b/blender/arm/props.py index 23e3f170..5533755f 100755 --- a/blender/arm/props.py +++ b/blender/arm/props.py @@ -11,7 +11,7 @@ import arm.proxy import arm.utils # Armory version -arm_version = '2021.6' +arm_version = '2021.7' arm_commit = '$Id$' def get_project_html5_copy(self): From 801668a0c2cf874835a801b2e20bb9854507001e Mon Sep 17 00:00:00 2001 From: N8n5h Date: Tue, 6 Jul 2021 10:39:07 -0300 Subject: [PATCH 202/264] Fix compiling error with shadow map atlas shadow size option Added 512 option to Inc so it doesn't fails compilation because of missing option as explained here https://github.com/armory3d/armory/issues/2252#issue-937328497 --- Sources/armory/renderpath/Inc.hx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Sources/armory/renderpath/Inc.hx b/Sources/armory/renderpath/Inc.hx index ad44d2d8..0e37fdef 100644 --- a/Sources/armory/renderpath/Inc.hx +++ b/Sources/armory/renderpath/Inc.hx @@ -715,7 +715,9 @@ class ShadowMapAtlas { public static inline function getMaxAtlasSize(type: String): Int { #if arm_shadowmap_atlas_single_map - #if (rp_shadowmap_atlas_max_size == 1024) + #if (rp_shadowmap_atlas_max_size == 512) + return 512; + #elseif (rp_shadowmap_atlas_max_size == 1024) return 1024; #elseif (rp_shadowmap_atlas_max_size == 2048) return 2048; @@ -742,7 +744,9 @@ class ShadowMapAtlas { #end } case "spot": { - #if (rp_shadowmap_atlas_max_size_spot == 1024) + #if (rp_shadowmap_atlas_max_size_spot == 512) + return 512; + #elseif (rp_shadowmap_atlas_max_size_spot == 1024) return 1024; #elseif (rp_shadowmap_atlas_max_size_spot == 2048) return 2048; @@ -755,7 +759,9 @@ class ShadowMapAtlas { #end } case "sun": { - #if (rp_shadowmap_atlas_max_size_sun == 1024) + #if (rp_shadowmap_atlas_max_size_sun == 512) + return 512; + #elseif (rp_shadowmap_atlas_max_size_sun == 1024) return 1024; #elseif (rp_shadowmap_atlas_max_size_sun == 2048) return 2048; @@ -768,7 +774,9 @@ class ShadowMapAtlas { #end } default: { - #if (rp_shadowmap_atlas_max_size == 1024) + #if (rp_shadowmap_atlas_max_size == 512) + return 512; + #elseif (rp_shadowmap_atlas_max_size == 1024) return 1024; #elseif (rp_shadowmap_atlas_max_size == 2048) return 2048; From 919512fad0c4f8e0b9838b5a04cb95781615f042 Mon Sep 17 00:00:00 2001 From: Henrique Date: Wed, 7 Jul 2021 16:33:20 -0300 Subject: [PATCH 203/264] Improve InputMap and add nodes to it --- .../armory/logicnode/GetInputMapKeyNode.hx | 24 + Sources/armory/logicnode/OnInputMapNode.hx | 35 ++ .../armory/logicnode/SetInputMapKeyNode.hx | 47 ++ Sources/armory/system/InputMap.hx | 452 ++++++------------ .../logicnode/input/LN_get_input_map_key.py | 15 + .../arm/logicnode/input/LN_on_input_map.py | 16 + .../logicnode/input/LN_set_input_map_key.py | 28 ++ 7 files changed, 322 insertions(+), 295 deletions(-) create mode 100644 Sources/armory/logicnode/GetInputMapKeyNode.hx create mode 100644 Sources/armory/logicnode/OnInputMapNode.hx create mode 100644 Sources/armory/logicnode/SetInputMapKeyNode.hx create mode 100644 blender/arm/logicnode/input/LN_get_input_map_key.py create mode 100644 blender/arm/logicnode/input/LN_on_input_map.py create mode 100644 blender/arm/logicnode/input/LN_set_input_map_key.py diff --git a/Sources/armory/logicnode/GetInputMapKeyNode.hx b/Sources/armory/logicnode/GetInputMapKeyNode.hx new file mode 100644 index 00000000..251a6d12 --- /dev/null +++ b/Sources/armory/logicnode/GetInputMapKeyNode.hx @@ -0,0 +1,24 @@ +package armory.logicnode; + +import armory.system.InputMap; + +class GetInputMapKeyNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + var inputMap = inputs[0].get(); + var key = inputs[1].get(); + + var k = InputMap.getInputMapKey(inputMap, key); + + if (k != null) { + if (from == 0) return k.scale; + else if (from == 1) return k.deadzone; + } + + return null; + } +} diff --git a/Sources/armory/logicnode/OnInputMapNode.hx b/Sources/armory/logicnode/OnInputMapNode.hx new file mode 100644 index 00000000..3e354f29 --- /dev/null +++ b/Sources/armory/logicnode/OnInputMapNode.hx @@ -0,0 +1,35 @@ +package armory.logicnode; + +import armory.system.InputMap; + +class OnInputMapNode extends LogicNode { + + var inputMap: Null; + + public function new(tree: LogicTree) { + super(tree); + + tree.notifyOnUpdate(update); + } + + function update() { + var i = inputs[0].get(); + + inputMap = InputMap.getInputMap(i); + + if (inputMap != null) { + if (inputMap.started()) { + runOutput(0); + } + + if (inputMap.released()) { + runOutput(1); + } + } + } + + override function get(from: Int): Dynamic { + if (from == 2) return inputMap.value(); + else return inputMap.lastKeyPressed; + } +} diff --git a/Sources/armory/logicnode/SetInputMapKeyNode.hx b/Sources/armory/logicnode/SetInputMapKeyNode.hx new file mode 100644 index 00000000..a449575d --- /dev/null +++ b/Sources/armory/logicnode/SetInputMapKeyNode.hx @@ -0,0 +1,47 @@ +package armory.logicnode; + +import armory.system.InputMap; + +class SetInputMapKeyNode extends LogicNode { + + public var property0: String; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var inputMap = inputs[1].get(); + var key = inputs[2].get(); + var scale = inputs[3].get(); + var deadzone = inputs[4].get(); + var index = inputs[5].get(); + + var i = InputMap.getInputMap(inputMap); + + if (i == null) { + i = InputMap.addInputMap(inputMap); + } + + var k = InputMap.getInputMapKey(inputMap, key); + + if (k == null) { + switch(property0) { + case "keyboard": k = i.addKeyboard(key, scale); + case "mouse": k = i.addMouse(key, scale, deadzone); + case "gamepad": { + k = i.addGamepad(key, scale, deadzone); + k.setIndex(index); + } + } + + } else { + k.key = key; + k.scale = scale; + k.deadzone = deadzone; + k.setIndex(index); + } + + runOutput(0); + } +} diff --git a/Sources/armory/system/InputMap.hx b/Sources/armory/system/InputMap.hx index 79b998e1..9b631cf9 100644 --- a/Sources/armory/system/InputMap.hx +++ b/Sources/armory/system/InputMap.hx @@ -4,347 +4,209 @@ import kha.FastFloat; import iron.system.Input; class InputMap { - var commands = new Map>>(); + + static var inputMaps(default, null) = new Map(); + + public var inputs(default, null) = new Array(); + public var lastKeyPressed(default, null) = ""; public function new() {} - public function addKeyboard(config: String) { - var command = new KeyboardCommand(); - return addCustomCommand(command, config); + public static function getInputMap(inputMap: String): Null { + if (inputMaps.exists(inputMap)) { + return inputMaps[inputMap]; + } + + return null; } - public function addGamepad(config: String) { - var command = new GamepadCommand(); - return addCustomCommand(command, config); + public static function addInputMap(inputMap: String): InputMap { + return inputMaps[inputMap] = new InputMap(); } - public function addCustomCommand(command: InputCommand, config: String) { - if (commands[config] == null) commands[config] = new Array(); - commands[config].push(command); - return command; - } -} - -class ActionMap extends InputMap { - - public inline function started(config: String) { - var started = false; - - for (c in commands[config]) { - if (c.started()) { - started = true; - break; + public static function getInputMapKey(inputMap: String, key: String): Null { + if (inputMaps.exists(inputMap)) { + for (i in inputMaps[inputMap].inputs) { + if (i.key == key) { + return i; + } } } - return started; + return null; } - public inline function released(config: String) { - var released = false; + public function addKeyboard(key: String, scale: FastFloat = 1.0): InputMapKey { + return addInput(new KeyboardKey(key, scale)); + } - for (c in commands[config]) { - if (c.released()) { - released = true; - break; - } + public function addMouse(key: String, scale: FastFloat = 1.0, deadzone: FastFloat = 0.0): InputMapKey { + return addInput(new MouseKey(key, scale, deadzone)); + } + + public function addGamepad(key: String, scale: FastFloat = 1.0, deadzone: FastFloat = 0.0): InputMapKey { + return addInput(new GamepadKey(key, scale, deadzone)); + } + + public function addInput(input: InputMapKey): InputMapKey { + inputs.push(input); + return input; + } + + public function removeInput(input: InputMapKey): Bool { + return inputs.remove(input); + } + + public function value(): FastFloat { + var v = 0.0; + + for (i in inputs) { + v += i.value(); } - return released; - } -} - -class AxisMap extends InputMap { - var scale: FastFloat = 1.0; - - public inline function getAxis(config: String) { - var axis = 0.0; - - for (c in commands[config]) { - var tempAxis = c.getAxis(); - - if (tempAxis != 0.0 && tempAxis != axis) { - axis += tempAxis; - scale = c.getScale(); - } - } - - return axis; - } - - public inline function getScale() { - return scale; - } -} - -class InputCommand { - var keys = new Array(); - var modifiers = new Array(); - var displacementKeys = new Array(); - var displacementModifiers = new Array(); - var deadzone: FastFloat = 0.0; - var scale: FastFloat = 1.0; - - public function new() {} - - public function setKeys(keys: Array) { - return this.keys = keys; - } - - public function setMods(modifiers: Array) { - return this.modifiers = modifiers; - } - - public function setDisplacementKeys(keys: Array) { - return displacementKeys = keys; - } - - public function setDisplacementMods(modifiers: Array) { - return displacementModifiers = modifiers; - } - - public function setDeadzone(deadzone: FastFloat) { - return this.deadzone = deadzone; - } - - public function setScale(scale: FastFloat) { - return this.scale = scale; - } - - public function getScale() { - return scale; + return v; } public function started() { + for (i in inputs) { + if (i.started()) { + lastKeyPressed = i.key; + return true; + } + } + return false; } public function released() { + for (i in inputs) { + if (i.released()) { + lastKeyPressed = i.key; + return true; + } + } + + return false; + } +} + +class InputMapKey { + + public var key: String; + public var scale: FastFloat; + public var deadzone: FastFloat; + + public function new(key: String, scale = 1.0, deadzone = 0.0) { + this.key = key.toLowerCase(); + this.scale = scale; + this.deadzone = deadzone; + } + + public function started(): Bool { return false; } - public function getAxis(): FastFloat { + public function released(): Bool { + return false; + } + + public function value(): FastFloat { return 0.0; } -} -class KeyboardCommand extends InputCommand { - var keyboard = Input.getKeyboard(); - var mouse = Input.getMouse(); + public function setIndex(index: Int) {} - public inline override function started() { - for (k in keys) { - if (keyboard.started(k)) { - for (m in modifiers) { - if (!keyboard.down(m)) return false; - } + function evalDeadzone(value: FastFloat): FastFloat { + var v = 0.0; - for (m in displacementModifiers) { - if (!mouse.down(m)) return false; - } + if (value > deadzone) { + v = value - deadzone; - return true; - } + } else if (value < -deadzone) { + v = value + deadzone; } - for (k in displacementKeys) { - if (mouse.started(k)) { - for (m in modifiers) { - if (!keyboard.down(m)) return false; - } - - for (m in displacementModifiers) { - if (!mouse.down(m)) return false; - } - - return true; - } - } - - return false; + return v * scale; } - public inline override function released() { - for (k in keys) { - if (keyboard.released(k)) { - for (m in modifiers) { - if (!keyboard.down(m)) return false; - } + function evalPressure(value: FastFloat): FastFloat { + var v = value - deadzone; - for (m in displacementModifiers) { - if (!mouse.down(m)) return false; - } + if (v > 0.0) { + v /= (1.0 - deadzone); - return true; - } + } else { + v = 0.0; } - - for (k in displacementKeys) { - if (mouse.released(k)) { - for (m in modifiers) { - if (!keyboard.down(m)) return false; - } - - for (m in displacementModifiers) { - if (!mouse.down(m)) return false; - } - - return true; - } - } - - return false; - } - - public inline override function getAxis() { - var axis = 0.0; - var movementX = mouse.movementX; - var movementY = mouse.movementY; - var wheelDelta = mouse.wheelDelta; - - for (k in keys) { - if (keyboard.down(k)) { - axis++; - break; - } - } - - for (m in modifiers) { - if (keyboard.down(m)) { - axis --; - break; - } - } - - for (k in displacementKeys) { - switch (k) { - case "moved x": if (movementX > deadzone) axis++; - case "moved y": if (movementY > deadzone) axis--; - case "wheel": if (wheelDelta < -deadzone) axis++; - case "movement x": if (movementX > deadzone) return movementX - deadzone; - case "movement y": if (movementY > deadzone) return movementY - deadzone; - default: { - if (mouse.down(k)) { - axis ++; - break; - } - } - } - } - - for (m in displacementModifiers) { - switch (m) { - case "moved x": if (movementX < -deadzone) axis--; - case "moved y": if (movementY < -deadzone) axis++; - case "wheel": if (wheelDelta > deadzone) axis--; - case "movement x": if (movementX < -deadzone) return movementX + deadzone; - case "movement y": if (movementY < -deadzone) return movementY + deadzone; - default: { - if (mouse.down(m)) { - axis --; - break; - } - } - } - } - - return axis > 1 ? 1 : axis < -1 ? -1 : axis; + + return v; } } -class GamepadCommand extends InputCommand { - var gamepad = Input.getGamepad(0); +class KeyboardKey extends InputMapKey { + + var kb = Input.getKeyboard(); public inline override function started() { - for (k in keys) { - if (gamepad.started(k)) { - for (m in modifiers) { - if (gamepad.down(m) < deadzone) return false; - } - - return true; - } - } - - return false; + return kb.started(key); } public inline override function released() { - for (k in keys) { - if (gamepad.released(k)) { - for (m in modifiers) { - if (gamepad.down(m) < deadzone) return false; - } - - return true; - } - } - - return false; + return kb.released(key); } - public inline override function getAxis() { - var axis = 0.0; - var rsMovementX = gamepad.rightStick.movementX; - var rsMovementY = gamepad.rightStick.movementY; - var lsMovementX = gamepad.leftStick.movementX; - var lsMovementY = gamepad.leftStick.movementY; - var rtPressure = gamepad.down("r2") > 0.0 ? (gamepad.down("r2") - deadzone) / (1 - deadzone) : 0.0; - var ltPressure = gamepad.down("l2") > 0.0 ? (gamepad.down("r2") - deadzone) / (1 - deadzone) : 0.0; - - for (k in keys) { - switch(k) { - case "rtPressure": axis += rtPressure; - case "ltPressure": axis += ltPressure; - default: { - if (gamepad.down(k) > deadzone) { - axis++; - break; - } - } - } - } - - for (m in modifiers) { - switch (m) { - case "rtPressure": axis -= rtPressure; - case "ltPressure": axis -= ltPressure; - default: { - if (gamepad.down(m) > deadzone) { - axis--; - break; - } - } - } - } - - for (k in displacementKeys) { - switch(k) { - case "rs moved x": if (rsMovementX > deadzone) axis++; - case "rs moved y": if (rsMovementY > deadzone) axis++; - case "ls moved x": if (lsMovementX > deadzone) axis++; - case "ls moved y": if (lsMovementY > deadzone) axis++; - case "rs movement x": if (rsMovementX > deadzone) return rsMovementX - deadzone; - case "rs movement y": if (rsMovementY > deadzone) return rsMovementY - deadzone; - case "ls movement x": if (lsMovementX > deadzone) return lsMovementX - deadzone; - case "ls movement y": if (lsMovementY > deadzone) return lsMovementY - deadzone; - } - } - - for (m in displacementModifiers) { - switch (m) { - case "rs moved x": if (rsMovementX < -deadzone) axis--; - case "rs moved y": if (rsMovementY < -deadzone) axis--; - case "ls moved x": if (lsMovementX < -deadzone) axis--; - case "ls moved y": if (lsMovementY < -deadzone) axis--; - case "rs movement x": if (rsMovementX < -deadzone) return rsMovementX + deadzone; - case "rs movement y": if (rsMovementY < -deadzone) return rsMovementY + deadzone; - case "ls movement x": if (lsMovementX < -deadzone) return lsMovementX + deadzone; - case "ls movement y": if (lsMovementY < -deadzone) return lsMovementY + deadzone; - - } - } - - return axis > 1 ? 1 : axis < -1 ? -1 : axis; + public inline override function value(): FastFloat { + return kb.down(key) ? scale : 0.0; } -} \ No newline at end of file +} + +class MouseKey extends InputMapKey { + + var m = Input.getMouse(); + + public inline override function started() { + return m.started(key); + } + + public inline override function released() { + return m.released(key); + } + + public override function value(): FastFloat { + return switch (key) { + case "movement x": evalDeadzone(m.movementX); + case "movement y": evalDeadzone(m.movementY); + case "wheel": evalDeadzone(m.wheelDelta); + default: m.down(key) ? scale : 0.0; + } + } +} + +class GamepadKey extends InputMapKey { + + var g = Input.getGamepad(); + + public inline override function started() { + return g.started(key); + } + + public inline override function released() { + return g.released(key); + } + + public override function value(): FastFloat { + return switch(key) { + case "ls movement x": evalDeadzone(g.leftStick.movementX); + case "ls movement y": evalDeadzone(g.leftStick.movementY); + case "rs movement x": evalDeadzone(g.rightStick.movementX); + case "rs movement y": evalDeadzone(g.rightStick.movementY); + case "lt pressure": evalDeadzone(evalPressure(g.down("l2"))); + case "rt pressure": evalDeadzone(evalPressure(g.down("r2"))); + default: evalDeadzone(g.down(key)); + } + } + + public override function setIndex(index: Int) { + g = Input.getGamepad(index); + } +} diff --git a/blender/arm/logicnode/input/LN_get_input_map_key.py b/blender/arm/logicnode/input/LN_get_input_map_key.py new file mode 100644 index 00000000..12d98935 --- /dev/null +++ b/blender/arm/logicnode/input/LN_get_input_map_key.py @@ -0,0 +1,15 @@ +from arm.logicnode.arm_nodes import * + +class GetInputMapKeyNode(ArmLogicTreeNode): + """Get key data if it exists in the input map.""" + bl_idname = 'LNGetInputMapKeyNode' + bl_label = 'Get Input Map Key' + arm_version = 1 + + def init(self, context): + super(GetInputMapKeyNode, self).init(context) + self.add_input('NodeSocketString', 'Input Map') + self.add_input('NodeSocketString', 'Key') + + self.add_output('NodeSocketFloat', 'Scale', default_value = 1.0) + self.add_output('NodeSocketFloat', 'Deadzone') \ No newline at end of file diff --git a/blender/arm/logicnode/input/LN_on_input_map.py b/blender/arm/logicnode/input/LN_on_input_map.py new file mode 100644 index 00000000..0cc540f5 --- /dev/null +++ b/blender/arm/logicnode/input/LN_on_input_map.py @@ -0,0 +1,16 @@ +from arm.logicnode.arm_nodes import * + +class OnInputMapNode(ArmLogicTreeNode): + """Send a signal if any input map key is started or released.""" + bl_idname = 'LNOnInputMapNode' + bl_label = 'On Input Map' + arm_version = 1 + + def init(self, context): + super(OnInputMapNode, self).init(context) + self.add_input('NodeSocketString', 'Input Map') + + self.add_output('ArmNodeSocketAction', 'Started') + self.add_output('ArmNodeSocketAction', 'Released') + self.add_output('NodeSocketFloat', 'Value') + self.add_output('NodeSocketString', 'Key Pressed') \ No newline at end of file diff --git a/blender/arm/logicnode/input/LN_set_input_map_key.py b/blender/arm/logicnode/input/LN_set_input_map_key.py new file mode 100644 index 00000000..a2157745 --- /dev/null +++ b/blender/arm/logicnode/input/LN_set_input_map_key.py @@ -0,0 +1,28 @@ +from arm.logicnode.arm_nodes import * + +class SetInputMapKeyNode(ArmLogicTreeNode): + """Set input map key.""" + bl_idname = 'LNSetInputMapKeyNode' + bl_label = 'Set Input Map Key' + arm_version = 1 + + property0: EnumProperty( + items = [('keyboard', 'Keyboard', 'Keyboard input'), + ('mouse', 'Mouse', 'Mouse input'), + ('gamepad', 'Gamepad', 'Gamepad input')], + name='', default='keyboard') + + def init(self, context): + super(SetInputMapKeyNode, self).init(context) + self.add_input('ArmNodeSocketAction', 'In') + self.add_input('NodeSocketString', 'Input Map') + self.add_input('NodeSocketString', 'Key') + self.add_input('NodeSocketFloat', 'Scale', default_value=1.0) + self.add_input('NodeSocketFloat', 'Deadzone') + self.add_input('NodeSocketInt', 'Index') + + self.add_output('ArmNodeSocketAction', 'Out') + + + def draw_buttons(self, context, layout): + layout.prop(self, 'property0') \ No newline at end of file From 43a574eb15b40d18dc6abedadfdd4a220ddd207d Mon Sep 17 00:00:00 2001 From: Henrique Date: Wed, 7 Jul 2021 16:51:07 -0300 Subject: [PATCH 204/264] Remove unnecessary read access --- Sources/armory/system/InputMap.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/armory/system/InputMap.hx b/Sources/armory/system/InputMap.hx index 9b631cf9..88b47ec9 100644 --- a/Sources/armory/system/InputMap.hx +++ b/Sources/armory/system/InputMap.hx @@ -5,7 +5,7 @@ import iron.system.Input; class InputMap { - static var inputMaps(default, null) = new Map(); + static var inputMaps = new Map(); public var inputs(default, null) = new Array(); public var lastKeyPressed(default, null) = ""; From daee309ad851c761f52c186fd1caa40c70553af0 Mon Sep 17 00:00:00 2001 From: Henrique Date: Wed, 7 Jul 2021 17:08:01 -0300 Subject: [PATCH 205/264] Add Remove Input Map Key node --- .../armory/logicnode/RemoveInputMapKeyNode.hx | 23 +++++++++++++++++++ .../input/LN_remove_input_map_key.py | 15 ++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 Sources/armory/logicnode/RemoveInputMapKeyNode.hx create mode 100644 blender/arm/logicnode/input/LN_remove_input_map_key.py diff --git a/Sources/armory/logicnode/RemoveInputMapKeyNode.hx b/Sources/armory/logicnode/RemoveInputMapKeyNode.hx new file mode 100644 index 00000000..ddd69518 --- /dev/null +++ b/Sources/armory/logicnode/RemoveInputMapKeyNode.hx @@ -0,0 +1,23 @@ +package armory.logicnode; + +import armory.system.InputMap; + +class RemoveInputMapKeyNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var inputMap = inputs[1].get(); + var key = inputs[2].get(); + + var k = InputMap.getInputMapKey(inputMap, key); + + if (k != null) { + if (InputMap.getInputMap(inputMap).removeInput(k)) { + runOutput(0); + } + } + } +} diff --git a/blender/arm/logicnode/input/LN_remove_input_map_key.py b/blender/arm/logicnode/input/LN_remove_input_map_key.py new file mode 100644 index 00000000..8821ef05 --- /dev/null +++ b/blender/arm/logicnode/input/LN_remove_input_map_key.py @@ -0,0 +1,15 @@ +from arm.logicnode.arm_nodes import * + +class RemoveInputMapKeyNode(ArmLogicTreeNode): + """Remove input map key.""" + bl_idname = 'LNRemoveInputMapKeyNode' + bl_label = 'Remove Input Map Key' + arm_version = 1 + + def init(self, context): + super(RemoveInputMapKeyNode, self).init(context) + self.add_input('ArmNodeSocketAction', 'In') + self.add_input('NodeSocketString', 'Input Map') + self.add_input('NodeSocketString', 'Key') + + self.add_output('ArmNodeSocketAction', 'Out') \ No newline at end of file From 55cf2896a2690993fe13306156289ca2779225aa Mon Sep 17 00:00:00 2001 From: Henrique Date: Wed, 7 Jul 2021 17:11:56 -0300 Subject: [PATCH 206/264] Cleanup --- .../armory/logicnode/RemoveInputMapKeyNode.hx | 2 +- .../armory/logicnode/SetInputMapKeyNode.hx | 1 - Sources/armory/system/InputMap.hx | 26 +++++++++---------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/Sources/armory/logicnode/RemoveInputMapKeyNode.hx b/Sources/armory/logicnode/RemoveInputMapKeyNode.hx index ddd69518..9a304ead 100644 --- a/Sources/armory/logicnode/RemoveInputMapKeyNode.hx +++ b/Sources/armory/logicnode/RemoveInputMapKeyNode.hx @@ -15,7 +15,7 @@ class RemoveInputMapKeyNode extends LogicNode { var k = InputMap.getInputMapKey(inputMap, key); if (k != null) { - if (InputMap.getInputMap(inputMap).removeInput(k)) { + if (InputMap.getInputMap(inputMap).removeKey(k)) { runOutput(0); } } diff --git a/Sources/armory/logicnode/SetInputMapKeyNode.hx b/Sources/armory/logicnode/SetInputMapKeyNode.hx index a449575d..e657456d 100644 --- a/Sources/armory/logicnode/SetInputMapKeyNode.hx +++ b/Sources/armory/logicnode/SetInputMapKeyNode.hx @@ -36,7 +36,6 @@ class SetInputMapKeyNode extends LogicNode { } } else { - k.key = key; k.scale = scale; k.deadzone = deadzone; k.setIndex(index); diff --git a/Sources/armory/system/InputMap.hx b/Sources/armory/system/InputMap.hx index 88b47ec9..6df4c721 100644 --- a/Sources/armory/system/InputMap.hx +++ b/Sources/armory/system/InputMap.hx @@ -7,7 +7,7 @@ class InputMap { static var inputMaps = new Map(); - public var inputs(default, null) = new Array(); + public var keys(default, null) = new Array(); public var lastKeyPressed(default, null) = ""; public function new() {} @@ -26,7 +26,7 @@ class InputMap { public static function getInputMapKey(inputMap: String, key: String): Null { if (inputMaps.exists(inputMap)) { - for (i in inputMaps[inputMap].inputs) { + for (i in inputMaps[inputMap].keys) { if (i.key == key) { return i; } @@ -37,30 +37,30 @@ class InputMap { } public function addKeyboard(key: String, scale: FastFloat = 1.0): InputMapKey { - return addInput(new KeyboardKey(key, scale)); + return addKey(new KeyboardKey(key, scale)); } public function addMouse(key: String, scale: FastFloat = 1.0, deadzone: FastFloat = 0.0): InputMapKey { - return addInput(new MouseKey(key, scale, deadzone)); + return addKey(new MouseKey(key, scale, deadzone)); } public function addGamepad(key: String, scale: FastFloat = 1.0, deadzone: FastFloat = 0.0): InputMapKey { - return addInput(new GamepadKey(key, scale, deadzone)); + return addKey(new GamepadKey(key, scale, deadzone)); } - public function addInput(input: InputMapKey): InputMapKey { - inputs.push(input); - return input; + public function addKey(key: InputMapKey): InputMapKey { + keys.push(key); + return key; } - public function removeInput(input: InputMapKey): Bool { - return inputs.remove(input); + public function removeKey(key: InputMapKey): Bool { + return keys.remove(key); } public function value(): FastFloat { var v = 0.0; - for (i in inputs) { + for (i in keys) { v += i.value(); } @@ -68,7 +68,7 @@ class InputMap { } public function started() { - for (i in inputs) { + for (i in keys) { if (i.started()) { lastKeyPressed = i.key; return true; @@ -79,7 +79,7 @@ class InputMap { } public function released() { - for (i in inputs) { + for (i in keys) { if (i.released()) { lastKeyPressed = i.key; return true; From db5aed465e56a5850ffd93439e8ca5ca5a019c20 Mon Sep 17 00:00:00 2001 From: Henrique Date: Thu, 8 Jul 2021 16:10:45 -0300 Subject: [PATCH 207/264] Add static method to remove keys --- .../armory/logicnode/RemoveInputMapKeyNode.hx | 8 ++--- Sources/armory/system/InputMap.hx | 36 +++++++++++++------ 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/Sources/armory/logicnode/RemoveInputMapKeyNode.hx b/Sources/armory/logicnode/RemoveInputMapKeyNode.hx index 9a304ead..44708c6d 100644 --- a/Sources/armory/logicnode/RemoveInputMapKeyNode.hx +++ b/Sources/armory/logicnode/RemoveInputMapKeyNode.hx @@ -12,12 +12,8 @@ class RemoveInputMapKeyNode extends LogicNode { var inputMap = inputs[1].get(); var key = inputs[2].get(); - var k = InputMap.getInputMapKey(inputMap, key); - - if (k != null) { - if (InputMap.getInputMap(inputMap).removeKey(k)) { - runOutput(0); - } + if (InputMap.removeInputMapKey(inputMap, key)) { + runOutput(0); } } } diff --git a/Sources/armory/system/InputMap.hx b/Sources/armory/system/InputMap.hx index 6df4c721..2bd2b493 100644 --- a/Sources/armory/system/InputMap.hx +++ b/Sources/armory/system/InputMap.hx @@ -26,9 +26,9 @@ class InputMap { public static function getInputMapKey(inputMap: String, key: String): Null { if (inputMaps.exists(inputMap)) { - for (i in inputMaps[inputMap].keys) { - if (i.key == key) { - return i; + for (k in inputMaps[inputMap].keys) { + if (k.key == key) { + return k; } } } @@ -36,6 +36,20 @@ class InputMap { return null; } + public static function removeInputMapKey(inputMap: String, key: String): Bool { + if (inputMaps.exists(inputMap)) { + var i = inputMaps[inputMap]; + + for (k in i.keys) { + if (k.key == key) { + return i.removeKey(k); + } + } + } + + return false; + } + public function addKeyboard(key: String, scale: FastFloat = 1.0): InputMapKey { return addKey(new KeyboardKey(key, scale)); } @@ -60,17 +74,17 @@ class InputMap { public function value(): FastFloat { var v = 0.0; - for (i in keys) { - v += i.value(); + for (k in keys) { + v += k.value(); } return v; } public function started() { - for (i in keys) { - if (i.started()) { - lastKeyPressed = i.key; + for (k in keys) { + if (k.started()) { + lastKeyPressed = k.key; return true; } } @@ -79,9 +93,9 @@ class InputMap { } public function released() { - for (i in keys) { - if (i.released()) { - lastKeyPressed = i.key; + for (k in keys) { + if (k.released()) { + lastKeyPressed = k.key; return true; } } From b452acaebb7841cde267336dddf9b2b51e462315 Mon Sep 17 00:00:00 2001 From: tong Date: Fri, 9 Jul 2021 22:04:50 +0200 Subject: [PATCH 208/264] Replace deprecated Std.is with Std.isOfType --- Sources/armory/logicnode/CompareNode.hx | 4 ++-- Sources/armory/logicnode/GateNode.hx | 4 ++-- Sources/armory/logicnode/GetTraitNameNode.hx | 4 ++-- Sources/armory/logicnode/PauseTraitNode.hx | 2 +- Sources/armory/logicnode/ResumeTraitNode.hx | 2 +- Sources/armory/logicnode/SetParentNode.hx | 2 +- Sources/armory/logicnode/SetTraitPausedNode.hx | 2 +- Sources/armory/trait/FollowCamera.hx | 2 +- Sources/armory/trait/physics/bullet/RigidBody.hx | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Sources/armory/logicnode/CompareNode.hx b/Sources/armory/logicnode/CompareNode.hx index 88dc97be..cfc02823 100644 --- a/Sources/armory/logicnode/CompareNode.hx +++ b/Sources/armory/logicnode/CompareNode.hx @@ -19,9 +19,9 @@ class CompareNode extends LogicNode { switch (property0) { case "Equal": - cond = Std.is(v1, Vec4) ? v1.equals(v2) : v1 == v2; + cond = Std.isOfType(v1, Vec4) ? v1.equals(v2) : v1 == v2; case "Almost Equal": - cond = Std.is(v1, Vec4) ? v1.almostEquals(v2, property1) : Math.abs(v1 - v2) < property1; + cond = Std.isOfType(v1, Vec4) ? v1.almostEquals(v2, property1) : Math.abs(v1 - v2) < property1; case "Greater": cond = v1 > v2; case "Greater Equal": diff --git a/Sources/armory/logicnode/GateNode.hx b/Sources/armory/logicnode/GateNode.hx index ca310419..ae0014e8 100644 --- a/Sources/armory/logicnode/GateNode.hx +++ b/Sources/armory/logicnode/GateNode.hx @@ -18,9 +18,9 @@ class GateNode extends LogicNode { switch (property0) { case "Equal": - cond = Std.is(v1, Vec4) ? v1.equals(v2) : v1 == v2; + cond = Std.isOfType(v1, Vec4) ? v1.equals(v2) : v1 == v2; case "Almost Equal": - cond = Std.is(v1, Vec4) ? v1.almostEquals(v2, property1) : Math.abs(v1 - v2) < property1; + cond = Std.isOfType(v1, Vec4) ? v1.almostEquals(v2, property1) : Math.abs(v1 - v2) < property1; case "Greater": cond = v1 > v2; case "Greater Equal": diff --git a/Sources/armory/logicnode/GetTraitNameNode.hx b/Sources/armory/logicnode/GetTraitNameNode.hx index dd1ded50..ca04f375 100644 --- a/Sources/armory/logicnode/GetTraitNameNode.hx +++ b/Sources/armory/logicnode/GetTraitNameNode.hx @@ -16,12 +16,12 @@ class GetTraitNameNode extends LogicNode { case 0: { // Check CanvasScript var cname = cast Type.resolveClass("armory.trait.internal.CanvasScript"); - if (Std.is(trait, cname)) { + if (Std.isOfType(trait, cname)) { return trait.cnvName; } // Check WasmScript var cname = cast Type.resolveClass("armory.trait.internal.WasmScript"); - if (Std.is(trait, cname)) { + if (Std.isOfType(trait, cname)) { return trait.wasmName; } // Other diff --git a/Sources/armory/logicnode/PauseTraitNode.hx b/Sources/armory/logicnode/PauseTraitNode.hx index a040f5d1..7dc3c084 100644 --- a/Sources/armory/logicnode/PauseTraitNode.hx +++ b/Sources/armory/logicnode/PauseTraitNode.hx @@ -8,7 +8,7 @@ class PauseTraitNode extends LogicNode { override function run(from: Int) { var trait: Dynamic = inputs[1].get(); - if (trait == null || !Std.is(trait, LogicTree)) return; + if (trait == null || !Std.isOfType(trait, LogicTree)) return; cast(trait, LogicTree).pause(); diff --git a/Sources/armory/logicnode/ResumeTraitNode.hx b/Sources/armory/logicnode/ResumeTraitNode.hx index c3c8b26b..c8a62011 100644 --- a/Sources/armory/logicnode/ResumeTraitNode.hx +++ b/Sources/armory/logicnode/ResumeTraitNode.hx @@ -8,7 +8,7 @@ class ResumeTraitNode extends LogicNode { override function run(from: Int) { var trait: Dynamic = inputs[1].get(); - if (trait == null || !Std.is(trait, LogicTree)) return; + if (trait == null || !Std.isOfType(trait, LogicTree)) return; cast(trait, LogicTree).resume(); diff --git a/Sources/armory/logicnode/SetParentNode.hx b/Sources/armory/logicnode/SetParentNode.hx index 8d37720d..81045a1e 100644 --- a/Sources/armory/logicnode/SetParentNode.hx +++ b/Sources/armory/logicnode/SetParentNode.hx @@ -14,7 +14,7 @@ class SetParentNode extends LogicNode { var parent: Object; var isUnparent = false; - if (Std.is(inputs[2].node, ObjectNode)) { + if (Std.isOfType(inputs[2].node, ObjectNode)) { var parentNode = cast(inputs[2].node, ObjectNode); isUnparent = parentNode.objectName == ""; } diff --git a/Sources/armory/logicnode/SetTraitPausedNode.hx b/Sources/armory/logicnode/SetTraitPausedNode.hx index 87099948..53327e1f 100644 --- a/Sources/armory/logicnode/SetTraitPausedNode.hx +++ b/Sources/armory/logicnode/SetTraitPausedNode.hx @@ -10,7 +10,7 @@ class SetTraitPausedNode extends LogicNode { var trait: Dynamic = inputs[1].get(); var paused: Bool = inputs[2].get(); - if (trait == null || !Std.is(trait, LogicTree)) return; + if (trait == null || !Std.isOfType(trait, LogicTree)) return; paused ? cast(trait, LogicTree).pause() : cast(trait, LogicTree).resume(); diff --git a/Sources/armory/trait/FollowCamera.hx b/Sources/armory/trait/FollowCamera.hx index 6cfdf84e..99df3c70 100644 --- a/Sources/armory/trait/FollowCamera.hx +++ b/Sources/armory/trait/FollowCamera.hx @@ -33,7 +33,7 @@ class FollowCamera extends iron.Trait { trace("FollowCamera error, unable to set target object"); } - if (Std.is(object, iron.object.CameraObject)) { + if (Std.isOfType(object, iron.object.CameraObject)) { disabled = true; trace("FollowCamera error, this trait should not be placed directly on a camera objet. It should be placed on another object such as an Empty. The camera should be placed as a child to the Empty object with offset, creating a camera boom."); } diff --git a/Sources/armory/trait/physics/bullet/RigidBody.hx b/Sources/armory/trait/physics/bullet/RigidBody.hx index 5ec1eb83..3c48adc7 100644 --- a/Sources/armory/trait/physics/bullet/RigidBody.hx +++ b/Sources/armory/trait/physics/bullet/RigidBody.hx @@ -149,7 +149,7 @@ class RigidBody extends iron.Trait { if (ready) return; ready = true; - if (!Std.is(object, MeshObject)) return; // No mesh data + if (!Std.isOfType(object, MeshObject)) return; // No mesh data transform = object.transform; physics = armory.trait.physics.PhysicsWorld.active; From 28d21bf35ffc94cb3269e3f159f8b1e03dc8a479 Mon Sep 17 00:00:00 2001 From: Henrique Date: Fri, 9 Jul 2021 21:42:12 -0300 Subject: [PATCH 209/264] Fix nullability --- Sources/armory/trait/internal/DebugConsole.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/armory/trait/internal/DebugConsole.hx b/Sources/armory/trait/internal/DebugConsole.hx index 25bfdc6e..cf7da8cf 100755 --- a/Sources/armory/trait/internal/DebugConsole.hx +++ b/Sources/armory/trait/internal/DebugConsole.hx @@ -594,7 +594,7 @@ class DebugConsole extends Trait { } function drawTiles(tile: ShadowMapTile, atlas: ShadowMapAtlas, atlasVisualSize: Float) { - var color = kha.Color.fromFloats(0.1, 0.1, 0.1); + var color: Null = kha.Color.fromFloats(0.1, 0.1, 0.1); var borderColor = color; var tileScale = (tile.size / atlas.sizew) * atlasVisualSize; //* 0.95; var x = (tile.coordsX / atlas.sizew) * atlasVisualSize; From e0ff256f40ffaf724969432122f787d4319c9abd Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Sat, 10 Jul 2021 19:20:56 +0200 Subject: [PATCH 210/264] make boolean objects nullable for static targets --- Sources/armory/logicnode/SetMaterialImageParamNode.hx | 4 +++- Sources/armory/logicnode/SetMaterialRgbParamNode.hx | 4 +++- Sources/armory/logicnode/SetMaterialValueParamNode.hx | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Sources/armory/logicnode/SetMaterialImageParamNode.hx b/Sources/armory/logicnode/SetMaterialImageParamNode.hx index 6b0c0ed8..c74e4553 100644 --- a/Sources/armory/logicnode/SetMaterialImageParamNode.hx +++ b/Sources/armory/logicnode/SetMaterialImageParamNode.hx @@ -13,10 +13,12 @@ class SetMaterialImageParamNode extends LogicNode { } override function run(from: Int) { + var perObject: Null; + var object = inputs[1].get(); if(object == null) return; - var perObject = inputs[2].get(); + perObject = inputs[2].get(); if(perObject == null) perObject = false; var mat = inputs[3].get(); diff --git a/Sources/armory/logicnode/SetMaterialRgbParamNode.hx b/Sources/armory/logicnode/SetMaterialRgbParamNode.hx index f0be3552..a5289b06 100644 --- a/Sources/armory/logicnode/SetMaterialRgbParamNode.hx +++ b/Sources/armory/logicnode/SetMaterialRgbParamNode.hx @@ -13,10 +13,12 @@ class SetMaterialRgbParamNode extends LogicNode { } override function run(from: Int) { + var perObject: Null; + var object = inputs[1].get(); if(object == null) return; - var perObject = inputs[2].get(); + perObject = inputs[2].get(); if(perObject == null) perObject = false; var mat = inputs[3].get(); diff --git a/Sources/armory/logicnode/SetMaterialValueParamNode.hx b/Sources/armory/logicnode/SetMaterialValueParamNode.hx index 8523bb8c..737365cf 100644 --- a/Sources/armory/logicnode/SetMaterialValueParamNode.hx +++ b/Sources/armory/logicnode/SetMaterialValueParamNode.hx @@ -12,10 +12,12 @@ class SetMaterialValueParamNode extends LogicNode { } override function run(from: Int) { + var perObject: Null; + var object = inputs[1].get(); if(object == null) return; - var perObject = inputs[2].get(); + perObject = inputs[2].get(); if(perObject == null) perObject = false; var mat = inputs[3].get(); From c0333db44fc8d59868417a8744d0fdf9032da7ca Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Sat, 10 Jul 2021 19:22:26 +0200 Subject: [PATCH 211/264] Fix typos. Fix fast float --- .../armory/trait/internal/UniformsManager.hx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Sources/armory/trait/internal/UniformsManager.hx b/Sources/armory/trait/internal/UniformsManager.hx index ed1ba2d5..b33624d9 100644 --- a/Sources/armory/trait/internal/UniformsManager.hx +++ b/Sources/armory/trait/internal/UniformsManager.hx @@ -23,7 +23,7 @@ class UniformsManager extends Trait{ static var sceneRemoveInitalized = false; - public var unifromExists = false; + public var uniformExists = false; public function new(){ super(); @@ -45,7 +45,7 @@ class UniformsManager extends Trait{ var exists = registerShaderUniforms(material); if(exists) { - unifromExists = true; + uniformExists = true; } } } @@ -90,7 +90,7 @@ class UniformsManager extends Trait{ // Register and map shader uniforms if it is an armory shader parameter public static function registerShaderUniforms(material: MaterialData) : Bool { - var unifromExist = false; + var uniformExist = false; if(! floatsMap.exists(Scene.active.root)) floatsMap.set(Scene.active.root, null); if(! vectorsMap.exists(Scene.active.root)) vectorsMap.set(Scene.active.root, null); @@ -98,15 +98,15 @@ class UniformsManager extends Trait{ for(context in material.shader.raw.contexts){ // For each context in shader for (constant in context.constants){ // For each constant in the context - if(constant.is_arm_parameter){ // Chack if armory parameter + if(constant.is_arm_parameter){ // Check if armory parameter - unifromExist = true; + uniformExist = true; var object = Scene.active.root; // Map default uniforms to scene root switch (constant.type){ case "float":{ var link = constant.link; - var value:Float = constant.float; + var value = constant.float; setFloatValue(material, object, link, value); register(Float); } @@ -126,9 +126,9 @@ class UniformsManager extends Trait{ } } for (texture in context.texture_units){ - if(texture.is_arm_parameter){ // Chack if armory parameter + if(texture.is_arm_parameter){ // Check if armory parameter - unifromExist = true; + uniformExist = true; var object = Scene.active.root; // Map default texture to scene root iron.data.Data.getImage(texture.default_image_file, function(image: kha.Image) { @@ -142,7 +142,7 @@ class UniformsManager extends Trait{ } } - return unifromExist; + return uniformExist; } From 96aa0ee890cc5c7e71853534dd828168b3c33299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 10 Jul 2021 21:46:44 +0200 Subject: [PATCH 212/264] Use custom sockets for default data types This allows to listen for socket updates for the live patch system --- .../logicnode/animation/LN_blend_action.py | 2 +- blender/arm/logicnode/animation/LN_bone_fk.py | 4 +- blender/arm/logicnode/animation/LN_bone_ik.py | 4 +- .../animation/LN_get_action_state.py | 6 +- .../animation/LN_get_tilesheet_state.py | 6 +- .../animation/LN_on_action_marker.py | 2 +- .../animation/LN_play_action_from.py | 8 +- .../logicnode/animation/LN_play_tilesheet.py | 2 +- .../animation/LN_set_action_paused.py | 2 +- .../animation/LN_set_action_speed.py | 2 +- .../logicnode/animation/LN_set_parent_bone.py | 2 +- .../animation/LN_set_particle_speed.py | 2 +- .../animation/LN_set_tilesheet_paused.py | 2 +- blender/arm/logicnode/arm_nodes.py | 21 +- blender/arm/logicnode/arm_sockets.py | 198 ++++++++++++++++-- blender/arm/logicnode/array/LN_array.py | 4 +- blender/arm/logicnode/array/LN_array_add.py | 8 +- .../arm/logicnode/array/LN_array_boolean.py | 4 +- blender/arm/logicnode/array/LN_array_color.py | 4 +- .../arm/logicnode/array/LN_array_contains.py | 4 +- blender/arm/logicnode/array/LN_array_float.py | 4 +- blender/arm/logicnode/array/LN_array_get.py | 4 +- .../arm/logicnode/array/LN_array_integer.py | 4 +- .../arm/logicnode/array/LN_array_length.py | 2 +- .../arm/logicnode/array/LN_array_loop_node.py | 4 +- .../arm/logicnode/array/LN_array_object.py | 2 +- blender/arm/logicnode/array/LN_array_pop.py | 2 +- .../array/LN_array_remove_by_index.py | 4 +- .../array/LN_array_remove_by_value.py | 6 +- blender/arm/logicnode/array/LN_array_set.py | 4 +- blender/arm/logicnode/array/LN_array_shift.py | 2 +- blender/arm/logicnode/array/LN_array_slice.py | 4 +- .../arm/logicnode/array/LN_array_splice.py | 4 +- .../arm/logicnode/array/LN_array_string.py | 4 +- .../arm/logicnode/array/LN_array_vector.py | 4 +- .../arm/logicnode/camera/LN_get_camera_fov.py | 2 +- .../arm/logicnode/camera/LN_set_camera_fov.py | 2 +- .../canvas/LN_get_canvas_checkbox.py | 4 +- .../canvas/LN_get_canvas_input_text.py | 4 +- .../canvas/LN_get_canvas_location.py | 6 +- .../canvas/LN_get_canvas_position.py | 4 +- .../canvas/LN_get_canvas_progress_bar.py | 6 +- .../canvas/LN_get_canvas_rotation.py | 4 +- .../logicnode/canvas/LN_get_canvas_scale.py | 6 +- .../logicnode/canvas/LN_get_canvas_slider.py | 4 +- .../logicnode/canvas/LN_get_canvas_visible.py | 4 +- .../logicnode/canvas/LN_on_canvas_element.py | 2 +- .../logicnode/canvas/LN_set_canvas_asset.py | 4 +- .../canvas/LN_set_canvas_checkbox.py | 4 +- .../canvas/LN_set_canvas_location.py | 6 +- .../canvas/LN_set_canvas_progress_bar.py | 6 +- .../canvas/LN_set_canvas_rotation.py | 4 +- .../logicnode/canvas/LN_set_canvas_scale.py | 6 +- .../logicnode/canvas/LN_set_canvas_slider.py | 4 +- .../logicnode/canvas/LN_set_canvas_text.py | 4 +- .../canvas/LN_set_canvas_text_color.py | 10 +- .../logicnode/canvas/LN_set_canvas_visible.py | 4 +- .../logicnode/deprecated/LN_get_mouse_lock.py | 2 +- .../deprecated/LN_get_mouse_visible.py | 2 +- .../logicnode/deprecated/LN_mouse_coords.py | 6 +- .../arm/logicnode/deprecated/LN_on_gamepad.py | 2 +- .../logicnode/deprecated/LN_pause_trait.py | 2 +- .../logicnode/deprecated/LN_play_action.py | 2 +- .../logicnode/deprecated/LN_resume_trait.py | 2 +- .../LN_rotate_object_around_axis.py | 4 +- .../logicnode/deprecated/LN_scale_object.py | 2 +- .../logicnode/deprecated/LN_set_mouse_lock.py | 2 +- .../deprecated/LN_set_mouse_visible.py | 2 +- .../deprecated/LN_set_object_material.py | 2 +- .../logicnode/deprecated/LN_surface_coords.py | 4 +- blender/arm/logicnode/event/LN_on_timer.py | 4 +- .../event/LN_send_event_to_object.py | 2 +- .../logicnode/event/LN_send_global_event.py | 2 +- blender/arm/logicnode/input/LN_gamepad.py | 4 +- .../arm/logicnode/input/LN_gamepad_coords.py | 14 +- .../logicnode/input/LN_get_cursor_location.py | 8 +- .../logicnode/input/LN_get_cursor_state.py | 6 +- .../logicnode/input/LN_get_mouse_movement.py | 18 +- .../logicnode/input/LN_get_touch_location.py | 8 +- .../logicnode/input/LN_get_touch_movement.py | 12 +- blender/arm/logicnode/input/LN_keyboard.py | 2 +- blender/arm/logicnode/input/LN_mouse.py | 2 +- blender/arm/logicnode/input/LN_on_swipe.py | 20 +- .../arm/logicnode/input/LN_on_tap_screen.py | 10 +- .../arm/logicnode/input/LN_sensor_coords.py | 2 +- .../logicnode/input/LN_set_cursor_state.py | 2 +- blender/arm/logicnode/input/LN_touch.py | 2 +- .../arm/logicnode/input/LN_virtual_button.py | 2 +- .../arm/logicnode/light/LN_set_light_color.py | 2 +- .../logicnode/light/LN_set_light_strength.py | 2 +- blender/arm/logicnode/logic/LN_branch.py | 2 +- .../arm/logicnode/logic/LN_call_function.py | 8 +- blender/arm/logicnode/logic/LN_function.py | 2 +- .../arm/logicnode/logic/LN_function_output.py | 2 +- blender/arm/logicnode/logic/LN_gate.py | 6 +- .../arm/logicnode/logic/LN_invert_boolean.py | 4 +- blender/arm/logicnode/logic/LN_is_false.py | 2 +- blender/arm/logicnode/logic/LN_is_not_null.py | 2 +- blender/arm/logicnode/logic/LN_is_null.py | 2 +- blender/arm/logicnode/logic/LN_is_true.py | 2 +- blender/arm/logicnode/logic/LN_loop.py | 6 +- blender/arm/logicnode/logic/LN_null.py | 2 +- .../logicnode/logic/LN_output_to_boolean.py | 2 +- .../arm/logicnode/logic/LN_switch_output.py | 4 +- .../arm/logicnode/logic/LN_value_changed.py | 2 +- blender/arm/logicnode/logic/LN_while_true.py | 2 +- .../material/LN_get_object_material.py | 4 +- blender/arm/logicnode/material/LN_material.py | 2 +- .../material/LN_set_material_image_param.py | 6 +- .../material/LN_set_material_rgb_param.py | 6 +- .../material/LN_set_material_value_param.py | 6 +- .../material/LN_set_object_material_slot.py | 4 +- blender/arm/logicnode/math/LN_clamp.py | 8 +- blender/arm/logicnode/math/LN_compare.py | 8 +- blender/arm/logicnode/math/LN_deg_to_rad.py | 4 +- blender/arm/logicnode/math/LN_map_range.py | 12 +- blender/arm/logicnode/math/LN_math.py | 12 +- .../arm/logicnode/math/LN_math_expression.py | 8 +- blender/arm/logicnode/math/LN_matrix_math.py | 6 +- blender/arm/logicnode/math/LN_mix.py | 8 +- blender/arm/logicnode/math/LN_mix_vector.py | 8 +- .../arm/logicnode/math/LN_quaternion_math.py | 74 +++---- blender/arm/logicnode/math/LN_rad_to_deg.py | 4 +- .../math/LN_screen_to_world_space.py | 24 +-- .../logicnode/math/LN_separate_quaternion.py | 10 +- blender/arm/logicnode/math/LN_separate_rgb.py | 8 +- blender/arm/logicnode/math/LN_separate_xyz.py | 8 +- blender/arm/logicnode/math/LN_vector_clamp.py | 8 +- blender/arm/logicnode/math/LN_vector_math.py | 40 ++-- .../math/LN_world_to_screen_space.py | 4 +- .../miscellaneous/LN_boolean_to_vector.py | 14 +- .../miscellaneous/LN_default_if_null.py | 6 +- .../miscellaneous/LN_get_application_time.py | 4 +- .../LN_get_debug_console_settings.py | 6 +- .../LN_get_display_resolution.py | 4 +- .../arm/logicnode/miscellaneous/LN_get_fps.py | 2 +- .../miscellaneous/LN_get_window_resolution.py | 4 +- .../LN_set_debug_console_settings.py | 4 +- .../miscellaneous/LN_set_time_scale.py | 2 +- .../arm/logicnode/miscellaneous/LN_sleep.py | 2 +- .../arm/logicnode/miscellaneous/LN_timer.py | 14 +- .../logicnode/native/LN_call_haxe_static.py | 4 +- .../native/LN_detect_mobile_browser.py | 2 +- blender/arm/logicnode/native/LN_expression.py | 2 +- .../logicnode/native/LN_get_haxe_property.py | 6 +- .../native/LN_get_system_language.py | 2 +- .../logicnode/native/LN_get_system_name.py | 12 +- blender/arm/logicnode/native/LN_loadUrl.py | 2 +- blender/arm/logicnode/native/LN_print.py | 2 +- blender/arm/logicnode/native/LN_read_file.py | 6 +- blender/arm/logicnode/native/LN_read_json.py | 6 +- .../arm/logicnode/native/LN_read_storage.py | 6 +- blender/arm/logicnode/native/LN_script.py | 2 +- .../logicnode/native/LN_set_haxe_property.py | 6 +- .../arm/logicnode/native/LN_set_vibrate.py | 2 +- blender/arm/logicnode/native/LN_write_file.py | 4 +- blender/arm/logicnode/native/LN_write_json.py | 4 +- .../arm/logicnode/native/LN_write_storage.py | 4 +- .../logicnode/navmesh/LN_go_to_location.py | 2 +- .../navmesh/LN_navigable_location.py | 2 +- .../navmesh/LN_pick_navmesh_location.py | 4 +- .../arm/logicnode/object/LN_get_distance.py | 2 +- .../logicnode/object/LN_get_object_by_name.py | 2 +- .../logicnode/object/LN_get_object_child.py | 2 +- .../logicnode/object/LN_get_object_mesh.py | 2 +- .../logicnode/object/LN_get_object_name.py | 2 +- .../object/LN_get_object_offscreen.py | 6 +- .../object/LN_get_object_property.py | 6 +- .../logicnode/object/LN_get_object_visible.py | 6 +- blender/arm/logicnode/object/LN_mesh.py | 2 +- .../object/LN_remove_object_parent.py | 2 +- .../logicnode/object/LN_set_object_mesh.py | 2 +- .../logicnode/object/LN_set_object_name.py | 2 +- .../object/LN_set_object_property.py | 4 +- .../logicnode/object/LN_set_object_visible.py | 4 +- .../arm/logicnode/object/LN_spawn_object.py | 4 +- .../object/LN_spawn_object_by_name.py | 6 +- .../logicnode/physics/LN_Add_rigid_body.py | 32 +-- .../physics/LN_add_physics_constraint.py | 32 +-- .../arm/logicnode/physics/LN_apply_force.py | 4 +- .../physics/LN_apply_force_at_location.py | 8 +- .../arm/logicnode/physics/LN_apply_impulse.py | 4 +- .../physics/LN_apply_impulse_at_location.py | 8 +- .../arm/logicnode/physics/LN_apply_torque.py | 4 +- .../physics/LN_apply_torque_impulse.py | 4 +- .../arm/logicnode/physics/LN_get_rb_data.py | 28 +-- .../logicnode/physics/LN_get_rb_velocity.py | 8 +- .../logicnode/physics/LN_get_world_gravity.py | 2 +- .../arm/logicnode/physics/LN_has_contact.py | 2 +- .../logicnode/physics/LN_has_contact_array.py | 2 +- .../physics/LN_physics_constraint.py | 14 +- blender/arm/logicnode/physics/LN_pick_rb.py | 6 +- blender/arm/logicnode/physics/LN_ray_cast.py | 10 +- .../logicnode/physics/LN_set_rb_friction.py | 2 +- .../physics/LN_set_rb_gravity_enabled.py | 2 +- .../logicnode/physics/LN_set_rb_velocity.py | 8 +- .../logicnode/physics/LN_set_world_gravity.py | 2 +- .../logicnode/physics/LN_volume_trigger.py | 2 +- .../LN_colorgrading_get_global_node.py | 14 +- .../LN_colorgrading_get_highlight_node.py | 12 +- .../LN_colorgrading_get_midtone_node.py | 10 +- .../LN_colorgrading_get_shadow_node.py | 12 +- .../LN_colorgrading_set_global_node.py | 28 +-- .../LN_colorgrading_set_highlight_node.py | 24 +-- .../LN_colorgrading_set_midtone_node.py | 22 +- .../LN_colorgrading_set_shadow_node.py | 24 +-- .../postprocess/LN_get_bloom_settings.py | 6 +- .../postprocess/LN_get_ca_settings.py | 4 +- .../postprocess/LN_get_camera_post_process.py | 20 +- .../LN_get_lenstexture_settings.py | 10 +- .../postprocess/LN_get_ssao_settings.py | 6 +- .../postprocess/LN_get_ssr_settings.py | 10 +- .../postprocess/LN_lenstexture_set.py | 10 +- .../postprocess/LN_set_bloom_settings.py | 6 +- .../postprocess/LN_set_ca_settings.py | 4 +- .../postprocess/LN_set_camera_post_process.py | 22 +- .../postprocess/LN_set_ssao_settings.py | 6 +- .../postprocess/LN_set_ssr_settings.py | 10 +- .../arm/logicnode/random/LN_random_boolean.py | 2 +- .../arm/logicnode/random/LN_random_choice.py | 2 +- .../arm/logicnode/random/LN_random_color.py | 2 +- .../arm/logicnode/random/LN_random_float.py | 8 +- .../arm/logicnode/random/LN_random_integer.py | 6 +- .../arm/logicnode/random/LN_random_vector.py | 6 +- .../renderpath/LN_set_post_process_quality.py | 2 +- .../renderpath/LN_set_shader_uniform.py | 10 +- blender/arm/logicnode/replacement.py | 77 +++++++ .../logicnode/scene/LN_create_collection.py | 2 +- .../arm/logicnode/scene/LN_get_collection.py | 2 +- .../logicnode/scene/LN_get_scene_active.py | 2 +- .../logicnode/scene/LN_remove_collection.py | 2 +- blender/arm/logicnode/scene/LN_scene.py | 2 +- .../logicnode/scene/LN_set_scene_active.py | 2 +- .../logicnode/scene/LN_spawn_collection.py | 2 +- blender/arm/logicnode/scene/LN_spawn_scene.py | 4 +- .../logicnode/string/LN_concatenate_string.py | 6 +- .../arm/logicnode/string/LN_parse_float.py | 4 +- .../arm/logicnode/string/LN_split_string.py | 4 +- blender/arm/logicnode/string/LN_string.py | 4 +- .../arm/logicnode/string/LN_string_case.py | 4 +- .../logicnode/string/LN_string_contains.py | 6 +- .../arm/logicnode/string/LN_string_length.py | 4 +- blender/arm/logicnode/string/LN_sub_string.py | 8 +- .../logicnode/trait/LN_add_trait_to_object.py | 2 +- .../logicnode/trait/LN_get_object_trait.py | 4 +- .../arm/logicnode/trait/LN_get_trait_name.py | 6 +- .../logicnode/trait/LN_get_trait_paused.py | 4 +- .../arm/logicnode/trait/LN_remove_trait.py | 2 +- blender/arm/logicnode/trait/LN_self_trait.py | 2 +- .../logicnode/trait/LN_set_trait_paused.py | 4 +- blender/arm/logicnode/trait/LN_trait.py | 2 +- .../transform/LN_append_transform.py | 2 +- .../transform/LN_get_object_location.py | 2 +- .../transform/LN_get_object_rotation.py | 12 +- .../transform/LN_get_object_scale.py | 2 +- .../transform/LN_get_object_transform.py | 2 +- .../transform/LN_get_world_orientation.py | 2 +- blender/arm/logicnode/transform/LN_look_at.py | 6 +- .../logicnode/transform/LN_rotate_object.py | 4 +- .../transform/LN_separate_transform.py | 8 +- .../transform/LN_set_object_location.py | 2 +- .../transform/LN_set_object_rotation.py | 4 +- .../transform/LN_set_object_scale.py | 2 +- .../transform/LN_set_object_transform.py | 2 +- .../arm/logicnode/transform/LN_transform.py | 8 +- .../logicnode/transform/LN_transform_math.py | 6 +- .../transform/LN_transform_to_vector.py | 8 +- .../transform/LN_translate_object.py | 4 +- .../transform/LN_translate_on_local_axis.py | 6 +- .../LN_vector_to_object_orientation.py | 4 +- .../LN_world_vector_to_local_space.py | 4 +- blender/arm/logicnode/variable/LN_boolean.py | 4 +- blender/arm/logicnode/variable/LN_color.py | 4 +- blender/arm/logicnode/variable/LN_dynamic.py | 2 +- blender/arm/logicnode/variable/LN_float.py | 4 +- blender/arm/logicnode/variable/LN_integer.py | 4 +- blender/arm/logicnode/variable/LN_mask.py | 4 +- .../arm/logicnode/variable/LN_quaternion.py | 14 +- .../arm/logicnode/variable/LN_set_variable.py | 4 +- blender/arm/logicnode/variable/LN_vector.py | 8 +- blender/arm/node_utils.py | 25 +++ blender/arm/nodes_logic.py | 2 +- blender/arm/props.py | 17 +- blender/arm/utils.py | 4 + 284 files changed, 1122 insertions(+), 834 deletions(-) diff --git a/blender/arm/logicnode/animation/LN_blend_action.py b/blender/arm/logicnode/animation/LN_blend_action.py index 087301e2..0d98d693 100644 --- a/blender/arm/logicnode/animation/LN_blend_action.py +++ b/blender/arm/logicnode/animation/LN_blend_action.py @@ -12,6 +12,6 @@ class BlendActionNode(ArmLogicTreeNode): self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmNodeSocketAnimAction', 'Action 1') self.add_input('ArmNodeSocketAnimAction', 'Action 2') - self.add_input('NodeSocketFloat', 'Factor', default_value = 0.5) + self.add_input('ArmFloatSocket', 'Factor', default_value = 0.5) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/animation/LN_bone_fk.py b/blender/arm/logicnode/animation/LN_bone_fk.py index e5aceff7..58f59910 100644 --- a/blender/arm/logicnode/animation/LN_bone_fk.py +++ b/blender/arm/logicnode/animation/LN_bone_fk.py @@ -11,7 +11,7 @@ class BoneFKNode(ArmLogicTreeNode): super(BoneFKNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketString', 'Bone') - self.add_input('NodeSocketShader', 'Transform') + self.add_input('ArmStringSocket', 'Bone') + self.add_input('ArmDynamicSocket', 'Transform') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/animation/LN_bone_ik.py b/blender/arm/logicnode/animation/LN_bone_ik.py index dd155015..00577537 100644 --- a/blender/arm/logicnode/animation/LN_bone_ik.py +++ b/blender/arm/logicnode/animation/LN_bone_ik.py @@ -11,7 +11,7 @@ class BoneIKNode(ArmLogicTreeNode): super(BoneIKNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketString', 'Bone') - self.add_input('NodeSocketVector', 'Goal') + self.add_input('ArmStringSocket', 'Bone') + self.add_input('ArmVectorSocket', 'Goal') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/animation/LN_get_action_state.py b/blender/arm/logicnode/animation/LN_get_action_state.py index d2c9f804..51e2d130 100644 --- a/blender/arm/logicnode/animation/LN_get_action_state.py +++ b/blender/arm/logicnode/animation/LN_get_action_state.py @@ -10,6 +10,6 @@ class AnimationStateNode(ArmLogicTreeNode): super(AnimationStateNode, self).init(context) self.add_input('ArmNodeSocketObject', 'Object') - self.add_output('NodeSocketString', 'Action') - self.add_output('NodeSocketInt', 'Frame') - self.add_output('NodeSocketBool', 'Is Paused') + self.add_output('ArmStringSocket', 'Action') + self.add_output('ArmIntSocket', 'Frame') + self.add_output('ArmBoolSocket', 'Is Paused') diff --git a/blender/arm/logicnode/animation/LN_get_tilesheet_state.py b/blender/arm/logicnode/animation/LN_get_tilesheet_state.py index b32e7d25..dadf0888 100644 --- a/blender/arm/logicnode/animation/LN_get_tilesheet_state.py +++ b/blender/arm/logicnode/animation/LN_get_tilesheet_state.py @@ -11,6 +11,6 @@ class GetTilesheetStateNode(ArmLogicTreeNode): super(GetTilesheetStateNode, self).init(context) self.add_input('ArmNodeSocketObject', 'Object') - self.add_output('NodeSocketString', 'Name') - self.add_output('NodeSocketInt', 'Frame') - self.add_output('NodeSocketBool', 'Is Paused') + self.add_output('ArmStringSocket', 'Name') + self.add_output('ArmIntSocket', 'Frame') + self.add_output('ArmBoolSocket', 'Is Paused') diff --git a/blender/arm/logicnode/animation/LN_on_action_marker.py b/blender/arm/logicnode/animation/LN_on_action_marker.py index a935015b..c26abc19 100644 --- a/blender/arm/logicnode/animation/LN_on_action_marker.py +++ b/blender/arm/logicnode/animation/LN_on_action_marker.py @@ -9,6 +9,6 @@ class OnActionMarkerNode(ArmLogicTreeNode): def init(self, context): super(OnActionMarkerNode, self).init(context) self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketString', 'Marker') + self.add_input('ArmStringSocket', 'Marker') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/animation/LN_play_action_from.py b/blender/arm/logicnode/animation/LN_play_action_from.py index b28221c1..732a7c40 100644 --- a/blender/arm/logicnode/animation/LN_play_action_from.py +++ b/blender/arm/logicnode/animation/LN_play_action_from.py @@ -11,10 +11,10 @@ class PlayActionFromNode(ArmLogicTreeNode): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmNodeSocketAnimAction', 'Action') - self.add_input('NodeSocketInt', 'Start Frame') - self.add_input('NodeSocketFloat', 'Blend', default_value = 0.25) - self.add_input('NodeSocketFloat', 'Speed', default_value = 1.0) - self.add_input('NodeSocketBool', 'Loop', default_value = False) + self.add_input('ArmIntSocket', 'Start Frame') + self.add_input('ArmFloatSocket', 'Blend', default_value = 0.25) + self.add_input('ArmFloatSocket', 'Speed', default_value = 1.0) + self.add_input('ArmBoolSocket', 'Loop', default_value = False) self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmNodeSocketAction', 'Done') diff --git a/blender/arm/logicnode/animation/LN_play_tilesheet.py b/blender/arm/logicnode/animation/LN_play_tilesheet.py index 533231d8..4af5d10a 100644 --- a/blender/arm/logicnode/animation/LN_play_tilesheet.py +++ b/blender/arm/logicnode/animation/LN_play_tilesheet.py @@ -11,7 +11,7 @@ class PlayTilesheetNode(ArmLogicTreeNode): super(PlayTilesheetNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketString', 'Name') + self.add_input('ArmStringSocket', 'Name') self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmNodeSocketAction', 'Done') diff --git a/blender/arm/logicnode/animation/LN_set_action_paused.py b/blender/arm/logicnode/animation/LN_set_action_paused.py index 6bdb5535..8df9b9ce 100644 --- a/blender/arm/logicnode/animation/LN_set_action_paused.py +++ b/blender/arm/logicnode/animation/LN_set_action_paused.py @@ -10,6 +10,6 @@ class SetActionPausedNode(ArmLogicTreeNode): super(SetActionPausedNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketBool', 'Paused') + self.add_input('ArmBoolSocket', 'Paused') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/animation/LN_set_action_speed.py b/blender/arm/logicnode/animation/LN_set_action_speed.py index c01fdd00..9a011883 100644 --- a/blender/arm/logicnode/animation/LN_set_action_speed.py +++ b/blender/arm/logicnode/animation/LN_set_action_speed.py @@ -10,6 +10,6 @@ class SetActionSpeedNode(ArmLogicTreeNode): super(SetActionSpeedNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketFloat', 'Speed', default_value=1.0) + self.add_input('ArmFloatSocket', 'Speed', default_value=1.0) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/animation/LN_set_parent_bone.py b/blender/arm/logicnode/animation/LN_set_parent_bone.py index fb954610..dc40384b 100644 --- a/blender/arm/logicnode/animation/LN_set_parent_bone.py +++ b/blender/arm/logicnode/animation/LN_set_parent_bone.py @@ -12,6 +12,6 @@ class SetParentBoneNode(ArmLogicTreeNode): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmNodeSocketObject', 'Parent', default_value='Parent') - self.add_input('NodeSocketString', 'Bone', default_value='Bone') + self.add_input('ArmStringSocket', 'Bone', default_value='Bone') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/animation/LN_set_particle_speed.py b/blender/arm/logicnode/animation/LN_set_particle_speed.py index 272a11f3..d4e614bd 100644 --- a/blender/arm/logicnode/animation/LN_set_particle_speed.py +++ b/blender/arm/logicnode/animation/LN_set_particle_speed.py @@ -10,6 +10,6 @@ class SetParticleSpeedNode(ArmLogicTreeNode): super(SetParticleSpeedNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketFloat', 'Speed', default_value=1.0) + self.add_input('ArmFloatSocket', 'Speed', default_value=1.0) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/animation/LN_set_tilesheet_paused.py b/blender/arm/logicnode/animation/LN_set_tilesheet_paused.py index 06322ad7..ef7f954a 100644 --- a/blender/arm/logicnode/animation/LN_set_tilesheet_paused.py +++ b/blender/arm/logicnode/animation/LN_set_tilesheet_paused.py @@ -11,6 +11,6 @@ class SetTilesheetPausedNode(ArmLogicTreeNode): super(SetTilesheetPausedNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketBool', 'Paused') + self.add_input('ArmBoolSocket', 'Paused') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/arm_nodes.py b/blender/arm/logicnode/arm_nodes.py index 152901c3..7c929f31 100644 --- a/blender/arm/logicnode/arm_nodes.py +++ b/blender/arm/logicnode/arm_nodes.py @@ -61,6 +61,9 @@ class ArmLogicTreeNode(bpy.types.Node): """ arm.live_patch.send_event('ln_update_prop', (self, prop_name)) + def on_socket_val_update(self, context: bpy.types.Context, socket: bpy.types.NodeSocket): + pass + def insert_link(self, link: bpy.types.NodeLink): """Called on *both* nodes when a link between two nodes is created.""" arm.live_patch.send_event('ln_insert_link', (self, link)) @@ -132,7 +135,7 @@ class ArmNodeAddInputButton(bpy.types.Operator): bl_options = {'UNDO', 'INTERNAL'} node_index: StringProperty(name='Node Index', default='') - socket_type: StringProperty(name='Socket Type', default='NodeSocketShader') + socket_type: StringProperty(name='Socket Type', default='ArmDynamicSocket') name_format: StringProperty(name='Name Format', default='Input {0}') index_name_offset: IntProperty(name='Index Name Offset', default=0) @@ -143,7 +146,7 @@ class ArmNodeAddInputButton(bpy.types.Operator): # Reset to default again for subsequent calls of this operator self.node_index = '' - self.socket_type = 'NodeSocketShader' + self.socket_type = 'ArmDynamicSocket' self.name_format = 'Input {0}' self.index_name_offset = 0 @@ -155,7 +158,7 @@ class ArmNodeAddInputValueButton(bpy.types.Operator): bl_label = 'Add Input' bl_options = {'UNDO', 'INTERNAL'} node_index: StringProperty(name='Node Index', default='') - socket_type: StringProperty(name='Socket Type', default='NodeSocketShader') + socket_type: StringProperty(name='Socket Type', default='ArmDynamicSocket') def execute(self, context): global array_nodes @@ -202,7 +205,7 @@ class ArmNodeAddOutputButton(bpy.types.Operator): bl_options = {'UNDO', 'INTERNAL'} node_index: StringProperty(name='Node Index', default='') - socket_type: StringProperty(name='Socket Type', default='NodeSocketShader') + socket_type: StringProperty(name='Socket Type', default='ArmDynamicSocket') name_format: StringProperty(name='Name Format', default='Output {0}') index_name_offset: IntProperty(name='Index Name Offset', default=0) @@ -213,7 +216,7 @@ class ArmNodeAddOutputButton(bpy.types.Operator): # Reset to default again for subsequent calls of this operator self.node_index = '' - self.socket_type = 'NodeSocketShader' + self.socket_type = 'ArmDynamicSocket' self.name_format = 'Output {0}' self.index_name_offset = 0 @@ -242,8 +245,8 @@ class ArmNodeAddInputOutputButton(bpy.types.Operator): bl_options = {'UNDO', 'INTERNAL'} node_index: StringProperty(name='Node Index', default='') - in_socket_type: StringProperty(name='In Socket Type', default='NodeSocketShader') - out_socket_type: StringProperty(name='Out Socket Type', default='NodeSocketShader') + in_socket_type: StringProperty(name='In Socket Type', default='ArmDynamicSocket') + out_socket_type: StringProperty(name='Out Socket Type', default='ArmDynamicSocket') in_name_format: StringProperty(name='In Name Format', default='Input {0}') out_name_format: StringProperty(name='Out Name Format', default='Output {0}') in_index_name_offset: IntProperty(name='Index Name Offset', default=0) @@ -258,8 +261,8 @@ class ArmNodeAddInputOutputButton(bpy.types.Operator): # Reset to default again for subsequent calls of this operator self.node_index = '' - self.in_socket_type = 'NodeSocketShader' - self.out_socket_type = 'NodeSocketShader' + self.in_socket_type = 'ArmDynamicSocket' + self.out_socket_type = 'ArmDynamicSocket' self.in_name_format = 'Input {0}' self.out_name_format = 'Output {0}' self.in_index_name_offset = 0 diff --git a/blender/arm/logicnode/arm_sockets.py b/blender/arm/logicnode/arm_sockets.py index 91f98476..58f2ea70 100644 --- a/blender/arm/logicnode/arm_sockets.py +++ b/blender/arm/logicnode/arm_sockets.py @@ -1,10 +1,14 @@ import bpy -from bpy.props import PointerProperty +from bpy.props import * from bpy.types import NodeSocket import arm.utils +def _on_update_socket(self, context): + self.node.on_socket_val_update(context, self) + + class ArmCustomSocket(NodeSocket): """ A custom socket that can be used to define more socket types for @@ -41,7 +45,7 @@ class ArmAnimActionSocket(ArmCustomSocket): arm_socket_type = 'STRING' default_value_get: PointerProperty(name='Action', type=bpy.types.Action) # legacy version of the line after this one - default_value_raw: PointerProperty(name='Action', type=bpy.types.Action) + default_value_raw: PointerProperty(name='Action', type=bpy.types.Action, update=_on_update_socket) def __init__(self): super().__init__() @@ -81,13 +85,114 @@ class ArmArraySocket(ArmCustomSocket): return 0.8, 0.4, 0.0, 1 +class ArmBoolSocket(ArmCustomSocket): + bl_idname = 'ArmBoolSocket' + bl_label = 'Boolean Socket' + arm_socket_type = 'BOOLEAN' + + default_value_raw: BoolProperty( + name='Value', + description='Input value used for unconnected socket', + update=_on_update_socket + ) + + def draw(self, context, layout, node, text): + draw_socket_layout(self, layout) + + def draw_color(self, context, node): + return 0.8, 0.651, 0.839, 1 + + def get_default_value(self): + return self.default_value_raw + + +class ArmColorSocket(ArmCustomSocket): + bl_idname = 'ArmColorSocket' + bl_label = 'Color Socket' + arm_socket_type = 'RGBA' + + default_value_raw: FloatVectorProperty( + name='Value', + size=4, + subtype='COLOR', + min=0.0, + max=1.0, + description='Input value used for unconnected socket', + update=_on_update_socket + ) + + def draw(self, context, layout, node, text): + draw_socket_layout(self, layout) + + def draw_color(self, context, node): + return 0.78, 0.78, 0.161, 1 + + def get_default_value(self): + return self.default_value_raw + + +class ArmDynamicSocket(ArmCustomSocket): + bl_idname = 'ArmDynamicSocket' + bl_label = 'Dynamic Socket' + arm_socket_type = 'NONE' + + def draw(self, context, layout, node, text): + layout.label(text=self.name) + + def draw_color(self, context, node): + return 0.388, 0.78, 0.388, 1 + + +class ArmFloatSocket(ArmCustomSocket): + bl_idname = 'ArmFloatSocket' + bl_label = 'Float Socket' + arm_socket_type = 'VALUE' + + default_value_raw: FloatProperty( + name='Value', + description='Input value used for unconnected socket', + precision=3, + update=_on_update_socket + ) + + def draw(self, context, layout, node, text): + draw_socket_layout(self, layout) + + def draw_color(self, context, node): + return 0.631, 0.631, 0.631, 1 + + def get_default_value(self): + return self.default_value_raw + + +class ArmIntSocket(ArmCustomSocket): + bl_idname = 'ArmIntSocket' + bl_label = 'Integer Socket' + arm_socket_type = 'INT' + + default_value_raw: IntProperty( + name='Value', + description='Input value used for unconnected socket', + update=_on_update_socket + ) + + def draw(self, context, layout, node, text): + draw_socket_layout(self, layout) + + def draw_color(self, context, node): + return 0.059, 0.522, 0.149, 1 + + def get_default_value(self): + return self.default_value_raw + + class ArmObjectSocket(ArmCustomSocket): bl_idname = 'ArmNodeSocketObject' bl_label = 'Object Socket' arm_socket_type = 'OBJECT' default_value_get: PointerProperty(name='Object', type=bpy.types.Object) # legacy version of the line after this one - default_value_raw: PointerProperty(name='Object', type=bpy.types.Object) + default_value_raw: PointerProperty(name='Object', type=bpy.types.Object, update=_on_update_socket) def __init__(self): super().__init__() @@ -115,15 +220,82 @@ class ArmObjectSocket(ArmCustomSocket): return 0.15, 0.55, 0.75, 1 -def register(): - bpy.utils.register_class(ArmActionSocket) - bpy.utils.register_class(ArmAnimActionSocket) - bpy.utils.register_class(ArmArraySocket) - bpy.utils.register_class(ArmObjectSocket) +class ArmStringSocket(ArmCustomSocket): + bl_idname = 'ArmStringSocket' + bl_label = 'String Socket' + arm_socket_type = 'STRING' + + default_value_raw: StringProperty( + name='Value', + description='Input value used for unconnected socket', + update=_on_update_socket + ) + + def draw(self, context, layout, node, text): + draw_socket_layout_split(self, layout) + + def draw_color(self, context, node): + return 0.439, 0.698, 1, 1 + + def get_default_value(self): + return self.default_value_raw -def unregister(): - bpy.utils.unregister_class(ArmObjectSocket) - bpy.utils.unregister_class(ArmArraySocket) - bpy.utils.unregister_class(ArmAnimActionSocket) - bpy.utils.unregister_class(ArmActionSocket) +class ArmVectorSocket(ArmCustomSocket): + bl_idname = 'ArmVectorSocket' + bl_label = 'Vector Socket' + arm_socket_type = 'VECTOR' + + default_value_raw: FloatVectorProperty( + name='Value', + size=3, + description='Input value used for unconnected socket', + update=_on_update_socket + ) + + def draw(self, context, layout, node, text): + if not self.is_output and not self.is_linked: + col = layout.column(align=True) + col.prop(self, 'default_value_raw', text='') + else: + layout.label(text=self.name) + + def draw_color(self, context, node): + return 0.388, 0.388, 0.78, 1 + + def get_default_value(self): + return self.default_value_raw + + +def draw_socket_layout(socket: bpy.types.NodeSocket, layout: bpy.types.UILayout, prop_name='default_value_raw'): + if not socket.is_output and not socket.is_linked: + layout.prop(socket, prop_name, text=socket.name) + else: + layout.label(text=socket.name) + + +def draw_socket_layout_split(socket: bpy.types.NodeSocket, layout: bpy.types.UILayout, prop_name='default_value_raw'): + if not socket.is_output and not socket.is_linked: + # Blender layouts use 0.4 splits + layout = layout.split(factor=0.4, align=True) + + layout.label(text=socket.name) + + if not socket.is_output and not socket.is_linked: + layout.prop(socket, prop_name, text='') + + +REG_CLASSES = ( + ArmActionSocket, + ArmAnimActionSocket, + ArmArraySocket, + ArmBoolSocket, + ArmColorSocket, + ArmDynamicSocket, + ArmFloatSocket, + ArmIntSocket, + ArmObjectSocket, + ArmStringSocket, + ArmVectorSocket, +) +register, unregister = bpy.utils.register_classes_factory(REG_CLASSES) diff --git a/blender/arm/logicnode/array/LN_array.py b/blender/arm/logicnode/array/LN_array.py index cb5cb451..39326e79 100644 --- a/blender/arm/logicnode/array/LN_array.py +++ b/blender/arm/logicnode/array/LN_array.py @@ -13,14 +13,14 @@ class ArrayNode(ArmLogicTreeNode): def init(self, context): super(ArrayNode, self).init(context) self.add_output('ArmNodeSocketArray', 'Array', is_var=True) - self.add_output('NodeSocketInt', 'Length') + self.add_output('ArmIntSocket', 'Length') def draw_buttons(self, context, layout): row = layout.row(align=True) op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketShader' + op.socket_type = 'ArmDynamicSocket' op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True) op2.node_index = str(id(self)) diff --git a/blender/arm/logicnode/array/LN_array_add.py b/blender/arm/logicnode/array/LN_array_add.py index 3bd708ea..acbc5959 100644 --- a/blender/arm/logicnode/array/LN_array_add.py +++ b/blender/arm/logicnode/array/LN_array_add.py @@ -19,9 +19,9 @@ class ArrayAddNode(ArmLogicTreeNode): super(ArrayAddNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketArray', 'Array') - self.add_input('NodeSocketBool', 'Modify Original', default_value=True) - self.add_input('NodeSocketBool', 'Unique Values') - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmBoolSocket', 'Modify Original', default_value=True) + self.add_input('ArmBoolSocket', 'Unique Values') + self.add_input('ArmDynamicSocket', 'Value') self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmNodeSocketArray', 'Array') @@ -31,6 +31,6 @@ class ArrayAddNode(ArmLogicTreeNode): op = row.operator('arm.node_add_input_value', text='Add Input', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketShader' + op.socket_type = 'ArmDynamicSocket' op2 = row.operator('arm.node_remove_input_value', text='', icon='X', emboss=True) op2.node_index = str(id(self)) diff --git a/blender/arm/logicnode/array/LN_array_boolean.py b/blender/arm/logicnode/array/LN_array_boolean.py index 6794a57e..ae355b09 100644 --- a/blender/arm/logicnode/array/LN_array_boolean.py +++ b/blender/arm/logicnode/array/LN_array_boolean.py @@ -14,14 +14,14 @@ class BooleanArrayNode(ArmLogicTreeNode): def init(self, context): super(BooleanArrayNode, self).init(context) self.add_output('ArmNodeSocketArray', 'Array', is_var=True) - self.add_output('NodeSocketInt', 'Length') + self.add_output('ArmIntSocket', 'Length') def draw_buttons(self, context, layout): row = layout.row(align=True) op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketBool' + op.socket_type = 'ArmBoolSocket' op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True) op2.node_index = str(id(self)) diff --git a/blender/arm/logicnode/array/LN_array_color.py b/blender/arm/logicnode/array/LN_array_color.py index f2066440..f569245a 100644 --- a/blender/arm/logicnode/array/LN_array_color.py +++ b/blender/arm/logicnode/array/LN_array_color.py @@ -14,14 +14,14 @@ class ColorArrayNode(ArmLogicTreeNode): def init(self, context): super(ColorArrayNode, self).init(context) self.add_output('ArmNodeSocketArray', 'Array', is_var=True) - self.add_output('NodeSocketInt', 'Length') + self.add_output('ArmIntSocket', 'Length') def draw_buttons(self, context, layout): row = layout.row(align=True) op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketColor' + op.socket_type = 'ArmColorSocket' op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True) op2.node_index = str(id(self)) diff --git a/blender/arm/logicnode/array/LN_array_contains.py b/blender/arm/logicnode/array/LN_array_contains.py index a9a3e463..b0a2bc53 100644 --- a/blender/arm/logicnode/array/LN_array_contains.py +++ b/blender/arm/logicnode/array/LN_array_contains.py @@ -9,6 +9,6 @@ class ArrayContainsNode(ArmLogicTreeNode): def init(self, context): super(ArrayContainsNode, self).init(context) self.add_input('ArmNodeSocketArray', 'Array') - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmDynamicSocket', 'Value') - self.add_output('NodeSocketBool', 'Contains') + self.add_output('ArmBoolSocket', 'Contains') diff --git a/blender/arm/logicnode/array/LN_array_float.py b/blender/arm/logicnode/array/LN_array_float.py index 1eeaf132..22e2019b 100644 --- a/blender/arm/logicnode/array/LN_array_float.py +++ b/blender/arm/logicnode/array/LN_array_float.py @@ -14,14 +14,14 @@ class FloatArrayNode(ArmLogicTreeNode): def init(self, context): super(FloatArrayNode, self).init(context) self.add_output('ArmNodeSocketArray', 'Array', is_var=True) - self.add_output('NodeSocketInt', 'Length') + self.add_output('ArmIntSocket', 'Length') def draw_buttons(self, context, layout): row = layout.row(align=True) op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketFloat' + op.socket_type = 'ArmFloatSocket' op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True) op2.node_index = str(id(self)) diff --git a/blender/arm/logicnode/array/LN_array_get.py b/blender/arm/logicnode/array/LN_array_get.py index 2ccfe176..bda50633 100644 --- a/blender/arm/logicnode/array/LN_array_get.py +++ b/blender/arm/logicnode/array/LN_array_get.py @@ -9,6 +9,6 @@ class ArrayGetNode(ArmLogicTreeNode): def init(self, context): super(ArrayGetNode, self).init(context) self.add_input('ArmNodeSocketArray', 'Array') - self.add_input('NodeSocketInt', 'Index') + self.add_input('ArmIntSocket', 'Index') - self.add_output('NodeSocketShader', 'Value') + self.add_output('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/array/LN_array_integer.py b/blender/arm/logicnode/array/LN_array_integer.py index 5e6cb2f1..f4e0aab8 100644 --- a/blender/arm/logicnode/array/LN_array_integer.py +++ b/blender/arm/logicnode/array/LN_array_integer.py @@ -14,14 +14,14 @@ class IntegerArrayNode(ArmLogicTreeNode): def init(self, context): super(IntegerArrayNode, self).init(context) self.add_output('ArmNodeSocketArray', 'Array') - self.add_output('NodeSocketInt', 'Length') + self.add_output('ArmIntSocket', 'Length') def draw_buttons(self, context, layout): row = layout.row(align=True) op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketInt' + op.socket_type = 'ArmIntSocket' op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True) op2.node_index = str(id(self)) diff --git a/blender/arm/logicnode/array/LN_array_length.py b/blender/arm/logicnode/array/LN_array_length.py index b6666533..c655ac86 100644 --- a/blender/arm/logicnode/array/LN_array_length.py +++ b/blender/arm/logicnode/array/LN_array_length.py @@ -10,4 +10,4 @@ class ArrayLengthNode(ArmLogicTreeNode): super(ArrayLengthNode, self).init(context) self.add_input('ArmNodeSocketArray', 'Array') - self.add_output('NodeSocketInt', 'Length') + self.add_output('ArmIntSocket', 'Length') diff --git a/blender/arm/logicnode/array/LN_array_loop_node.py b/blender/arm/logicnode/array/LN_array_loop_node.py index 1e791989..18207c8e 100644 --- a/blender/arm/logicnode/array/LN_array_loop_node.py +++ b/blender/arm/logicnode/array/LN_array_loop_node.py @@ -13,6 +13,6 @@ class ArrayLoopNode(ArmLogicTreeNode): self.add_input('ArmNodeSocketArray', 'Array') self.add_output('ArmNodeSocketAction', 'Loop') - self.add_output('NodeSocketShader', 'Value') - self.add_output('NodeSocketInt', 'Index') + self.add_output('ArmDynamicSocket', 'Value') + self.add_output('ArmIntSocket', 'Index') self.add_output('ArmNodeSocketAction', 'Done') diff --git a/blender/arm/logicnode/array/LN_array_object.py b/blender/arm/logicnode/array/LN_array_object.py index 8328ebc7..71c55d89 100644 --- a/blender/arm/logicnode/array/LN_array_object.py +++ b/blender/arm/logicnode/array/LN_array_object.py @@ -14,7 +14,7 @@ class ObjectArrayNode(ArmLogicTreeNode): def init(self, context): super(ObjectArrayNode, self).init(context) self.add_output('ArmNodeSocketArray', 'Array', is_var=True) - self.add_output('NodeSocketInt', 'Length') + self.add_output('ArmIntSocket', 'Length') def draw_buttons(self, context, layout): row = layout.row(align=True) diff --git a/blender/arm/logicnode/array/LN_array_pop.py b/blender/arm/logicnode/array/LN_array_pop.py index 8bd93a8d..9708b343 100644 --- a/blender/arm/logicnode/array/LN_array_pop.py +++ b/blender/arm/logicnode/array/LN_array_pop.py @@ -12,4 +12,4 @@ class ArrayPopNode(ArmLogicTreeNode): super(ArrayPopNode, self).init(context) self.add_input('ArmNodeSocketArray', 'Array') - self.add_output('NodeSocketShader', 'Value') + self.add_output('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/array/LN_array_remove_by_index.py b/blender/arm/logicnode/array/LN_array_remove_by_index.py index 9a81760b..a07c864c 100644 --- a/blender/arm/logicnode/array/LN_array_remove_by_index.py +++ b/blender/arm/logicnode/array/LN_array_remove_by_index.py @@ -12,7 +12,7 @@ class ArrayRemoveIndexNode(ArmLogicTreeNode): super(ArrayRemoveIndexNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketArray', 'Array') - self.add_input('NodeSocketInt', 'Index') + self.add_input('ArmIntSocket', 'Index') self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketShader', 'Value') + self.add_output('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/array/LN_array_remove_by_value.py b/blender/arm/logicnode/array/LN_array_remove_by_value.py index f7634ea7..8ab646fd 100644 --- a/blender/arm/logicnode/array/LN_array_remove_by_value.py +++ b/blender/arm/logicnode/array/LN_array_remove_by_value.py @@ -15,16 +15,16 @@ class ArrayRemoveValueNode(ArmLogicTreeNode): super(ArrayRemoveValueNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketArray', 'Array') - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmDynamicSocket', 'Value') self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketShader', 'Value') + self.add_output('ArmDynamicSocket', 'Value') # def draw_buttons(self, context, layout): # row = layout.row(align=True) # op = row.operator('arm.node_add_input_value', text='New', icon='PLUS', emboss=True) # op.node_index = str(id(self)) - # op.socket_type = 'NodeSocketShader' + # op.socket_type = 'ArmDynamicSocket' # op2 = row.operator('arm.node_remove_input_value', text='', icon='X', emboss=True) # op2.node_index = str(id(self)) diff --git a/blender/arm/logicnode/array/LN_array_set.py b/blender/arm/logicnode/array/LN_array_set.py index 7c1f0197..c0fb2293 100644 --- a/blender/arm/logicnode/array/LN_array_set.py +++ b/blender/arm/logicnode/array/LN_array_set.py @@ -10,7 +10,7 @@ class ArraySetNode(ArmLogicTreeNode): super(ArraySetNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketArray', 'Array') - self.add_input('NodeSocketInt', 'Index') - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmIntSocket', 'Index') + self.add_input('ArmDynamicSocket', 'Value') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/array/LN_array_shift.py b/blender/arm/logicnode/array/LN_array_shift.py index 6a40faf2..d1d31b37 100644 --- a/blender/arm/logicnode/array/LN_array_shift.py +++ b/blender/arm/logicnode/array/LN_array_shift.py @@ -12,4 +12,4 @@ class ArrayShiftNode(ArmLogicTreeNode): super(ArrayShiftNode, self).init(context) self.add_input('ArmNodeSocketArray', 'Array') - self.add_output('NodeSocketShader', 'Value') + self.add_output('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/array/LN_array_slice.py b/blender/arm/logicnode/array/LN_array_slice.py index 29ca2533..87da4910 100644 --- a/blender/arm/logicnode/array/LN_array_slice.py +++ b/blender/arm/logicnode/array/LN_array_slice.py @@ -11,7 +11,7 @@ class ArraySliceNode(ArmLogicTreeNode): def init(self, context): super(ArraySliceNode, self).init(context) self.add_input('ArmNodeSocketArray', 'Array') - self.add_input('NodeSocketInt', 'Index') - self.add_input('NodeSocketInt', 'End') + self.add_input('ArmIntSocket', 'Index') + self.add_input('ArmIntSocket', 'End') self.add_output('ArmNodeSocketArray', 'Array') diff --git a/blender/arm/logicnode/array/LN_array_splice.py b/blender/arm/logicnode/array/LN_array_splice.py index 06aa7481..ea1c28f8 100644 --- a/blender/arm/logicnode/array/LN_array_splice.py +++ b/blender/arm/logicnode/array/LN_array_splice.py @@ -12,7 +12,7 @@ class ArraySpliceNode(ArmLogicTreeNode): super(ArraySpliceNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketArray', 'Array') - self.add_input('NodeSocketInt', 'Index') - self.add_input('NodeSocketInt', 'Length') + self.add_input('ArmIntSocket', 'Index') + self.add_input('ArmIntSocket', 'Length') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/array/LN_array_string.py b/blender/arm/logicnode/array/LN_array_string.py index d1a82bb4..2052a9db 100644 --- a/blender/arm/logicnode/array/LN_array_string.py +++ b/blender/arm/logicnode/array/LN_array_string.py @@ -14,14 +14,14 @@ class StringArrayNode(ArmLogicTreeNode): def init(self, context): super(StringArrayNode, self).init(context) self.add_output('ArmNodeSocketArray', 'Array', is_var=True) - self.add_output('NodeSocketInt', 'Length') + self.add_output('ArmIntSocket', 'Length') def draw_buttons(self, context, layout): row = layout.row(align=True) op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketString' + op.socket_type = 'ArmStringSocket' op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True) op2.node_index = str(id(self)) diff --git a/blender/arm/logicnode/array/LN_array_vector.py b/blender/arm/logicnode/array/LN_array_vector.py index 94ac05e0..75b08e59 100644 --- a/blender/arm/logicnode/array/LN_array_vector.py +++ b/blender/arm/logicnode/array/LN_array_vector.py @@ -14,14 +14,14 @@ class VectorArrayNode(ArmLogicTreeNode): def init(self, context): super(VectorArrayNode, self).init(context) self.add_output('ArmNodeSocketArray', 'Array', is_var=True) - self.add_output('NodeSocketInt', 'Length') + self.add_output('ArmIntSocket', 'Length') def draw_buttons(self, context, layout): row = layout.row(align=True) op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketVector' + op.socket_type = 'ArmVectorSocket' op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True) op2.node_index = str(id(self)) diff --git a/blender/arm/logicnode/camera/LN_get_camera_fov.py b/blender/arm/logicnode/camera/LN_get_camera_fov.py index d35c3181..f6dc4426 100644 --- a/blender/arm/logicnode/camera/LN_get_camera_fov.py +++ b/blender/arm/logicnode/camera/LN_get_camera_fov.py @@ -12,4 +12,4 @@ class GetCameraFovNode(ArmLogicTreeNode): super(GetCameraFovNode, self).init(context) self.add_input('ArmNodeSocketObject', 'Object') - self.add_output('NodeSocketFloat', 'FOV') + self.add_output('ArmFloatSocket', 'FOV') diff --git a/blender/arm/logicnode/camera/LN_set_camera_fov.py b/blender/arm/logicnode/camera/LN_set_camera_fov.py index 4785c18c..2a5103ea 100644 --- a/blender/arm/logicnode/camera/LN_set_camera_fov.py +++ b/blender/arm/logicnode/camera/LN_set_camera_fov.py @@ -12,6 +12,6 @@ class SetCameraFovNode(ArmLogicTreeNode): super(SetCameraFovNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Camera') - self.add_input('NodeSocketFloat', 'FOV', default_value=0.9) + self.add_input('ArmFloatSocket', 'FOV', default_value=0.9) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_checkbox.py b/blender/arm/logicnode/canvas/LN_get_canvas_checkbox.py index 027b2015..a8860a36 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_checkbox.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_checkbox.py @@ -8,6 +8,6 @@ class CanvasGetCheckboxNode(ArmLogicTreeNode): def init(self, context): super(CanvasGetCheckboxNode, self).init(context) - self.add_input('NodeSocketString', 'Element') + self.add_input('ArmStringSocket', 'Element') - self.add_output('NodeSocketBool', 'Is Checked') + self.add_output('ArmBoolSocket', 'Is Checked') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_input_text.py b/blender/arm/logicnode/canvas/LN_get_canvas_input_text.py index 09e10e71..69938c5c 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_input_text.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_input_text.py @@ -8,6 +8,6 @@ class CanvasGetInputTextNode(ArmLogicTreeNode): def init(self, context): super(CanvasGetInputTextNode, self).init(context) - self.add_input('NodeSocketString', 'Element') + self.add_input('ArmStringSocket', 'Element') - self.add_output('NodeSocketString', 'Text') + self.add_output('ArmStringSocket', 'Text') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_location.py b/blender/arm/logicnode/canvas/LN_get_canvas_location.py index 3ea3c2d8..21e38a1b 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_location.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_location.py @@ -9,8 +9,8 @@ class CanvasGetLocationNode(ArmLogicTreeNode): def init(self, context): super(CanvasGetLocationNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') + self.add_input('ArmStringSocket', 'Element') self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketInt', 'X') - self.add_output('NodeSocketInt', 'Y') + self.add_output('ArmIntSocket', 'X') + self.add_output('ArmIntSocket', 'Y') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_position.py b/blender/arm/logicnode/canvas/LN_get_canvas_position.py index d7704fd6..66ae4db6 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_position.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_position.py @@ -8,6 +8,6 @@ class CanvasGetPositionNode(ArmLogicTreeNode): def init(self, context): super(CanvasGetPositionNode, self).init(context) - self.add_input('NodeSocketString', 'Element') + self.add_input('ArmStringSocket', 'Element') - self.add_output('NodeSocketInt', 'Position') + self.add_output('ArmIntSocket', 'Position') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_progress_bar.py b/blender/arm/logicnode/canvas/LN_get_canvas_progress_bar.py index b7aa0795..493a740d 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_progress_bar.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_progress_bar.py @@ -9,8 +9,8 @@ class CanvasGetPBNode(ArmLogicTreeNode): def init(self, context): super(CanvasGetPBNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') + self.add_input('ArmStringSocket', 'Element') self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketInt', 'At') - self.add_output('NodeSocketInt', 'Max') + self.add_output('ArmIntSocket', 'At') + self.add_output('ArmIntSocket', 'Max') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_rotation.py b/blender/arm/logicnode/canvas/LN_get_canvas_rotation.py index 342c9288..bde56b39 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_rotation.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_rotation.py @@ -9,7 +9,7 @@ class CanvasGetRotationNode(ArmLogicTreeNode): def init(self, context): super(CanvasGetRotationNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') + self.add_input('ArmStringSocket', 'Element') self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketFloat', 'Rad') + self.add_output('ArmFloatSocket', 'Rad') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_scale.py b/blender/arm/logicnode/canvas/LN_get_canvas_scale.py index 631a7185..fad278f2 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_scale.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_scale.py @@ -9,8 +9,8 @@ class CanvasGetScaleNode(ArmLogicTreeNode): def init(self, context): super(CanvasGetScaleNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') + self.add_input('ArmStringSocket', 'Element') self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketInt', 'Height') - self.add_output('NodeSocketInt', 'Width') + self.add_output('ArmIntSocket', 'Height') + self.add_output('ArmIntSocket', 'Width') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_slider.py b/blender/arm/logicnode/canvas/LN_get_canvas_slider.py index 70ccbac3..d6d44535 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_slider.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_slider.py @@ -8,6 +8,6 @@ class CanvasGetSliderNode(ArmLogicTreeNode): def init(self, context): super(CanvasGetSliderNode, self).init(context) - self.add_input('NodeSocketString', 'Element') + self.add_input('ArmStringSocket', 'Element') - self.add_output('NodeSocketFloat', 'Float') + self.add_output('ArmFloatSocket', 'Float') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_visible.py b/blender/arm/logicnode/canvas/LN_get_canvas_visible.py index 6c983e62..f930146f 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_visible.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_visible.py @@ -11,6 +11,6 @@ class CanvasGetVisibleNode(ArmLogicTreeNode): def init(self, context): super(CanvasGetVisibleNode, self).init(context) - self.inputs.new('NodeSocketString', 'Element') + self.inputs.new('ArmStringSocket', 'Element') - self.outputs.new('NodeSocketBool', 'Is Visible') + self.outputs.new('ArmBoolSocket', 'Is Visible') diff --git a/blender/arm/logicnode/canvas/LN_on_canvas_element.py b/blender/arm/logicnode/canvas/LN_on_canvas_element.py index ba1301c1..a72501a5 100644 --- a/blender/arm/logicnode/canvas/LN_on_canvas_element.py +++ b/blender/arm/logicnode/canvas/LN_on_canvas_element.py @@ -26,7 +26,7 @@ class OnCanvasElementNode(ArmLogicTreeNode): def init(self, context): super(OnCanvasElementNode, self).init(context) - self.add_input('NodeSocketString', 'Element') + self.add_input('ArmStringSocket', 'Element') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_asset.py b/blender/arm/logicnode/canvas/LN_set_canvas_asset.py index 174b4b01..077ece2e 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_asset.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_asset.py @@ -9,7 +9,7 @@ class CanvasSetAssetNode(ArmLogicTreeNode): def init(self, context): super(CanvasSetAssetNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') - self.add_input('NodeSocketString', 'Asset') + self.add_input('ArmStringSocket', 'Element') + self.add_input('ArmStringSocket', 'Asset') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_checkbox.py b/blender/arm/logicnode/canvas/LN_set_canvas_checkbox.py index a3759a68..293200a5 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_checkbox.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_checkbox.py @@ -9,7 +9,7 @@ class CanvasSetCheckBoxNode(ArmLogicTreeNode): def init(self, context): super(CanvasSetCheckBoxNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') - self.add_input('NodeSocketBool', 'Check') + self.add_input('ArmStringSocket', 'Element') + self.add_input('ArmBoolSocket', 'Check') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_location.py b/blender/arm/logicnode/canvas/LN_set_canvas_location.py index 12886e37..cfb43449 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_location.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_location.py @@ -9,8 +9,8 @@ class CanvasSetLocationNode(ArmLogicTreeNode): def init(self, context): super(CanvasSetLocationNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') - self.add_input('NodeSocketFloat', 'X') - self.add_input('NodeSocketFloat', 'Y') + self.add_input('ArmStringSocket', 'Element') + self.add_input('ArmFloatSocket', 'X') + self.add_input('ArmFloatSocket', 'Y') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_progress_bar.py b/blender/arm/logicnode/canvas/LN_set_canvas_progress_bar.py index bd39bd1d..38ae0530 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_progress_bar.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_progress_bar.py @@ -9,8 +9,8 @@ class CanvasSetPBNode(ArmLogicTreeNode): def init(self, context): super(CanvasSetPBNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') - self.add_input('NodeSocketInt', 'At') - self.add_input('NodeSocketInt', 'Max') + self.add_input('ArmStringSocket', 'Element') + self.add_input('ArmIntSocket', 'At') + self.add_input('ArmIntSocket', 'Max') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_rotation.py b/blender/arm/logicnode/canvas/LN_set_canvas_rotation.py index 4379f61a..dae5deaf 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_rotation.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_rotation.py @@ -9,7 +9,7 @@ class CanvasSetRotationNode(ArmLogicTreeNode): def init(self, context): super(CanvasSetRotationNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') - self.add_input('NodeSocketFloat', 'Rad') + self.add_input('ArmStringSocket', 'Element') + self.add_input('ArmFloatSocket', 'Rad') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_scale.py b/blender/arm/logicnode/canvas/LN_set_canvas_scale.py index 1718c9ad..714c0030 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_scale.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_scale.py @@ -9,8 +9,8 @@ class CanvasSetScaleNode(ArmLogicTreeNode): def init(self, context): super(CanvasSetScaleNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') - self.add_input('NodeSocketInt', 'Height') - self.add_input('NodeSocketInt', 'Width') + self.add_input('ArmStringSocket', 'Element') + self.add_input('ArmIntSocket', 'Height') + self.add_input('ArmIntSocket', 'Width') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_slider.py b/blender/arm/logicnode/canvas/LN_set_canvas_slider.py index 7c7cadda..7f48cce3 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_slider.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_slider.py @@ -9,7 +9,7 @@ class CanvasSetSliderNode(ArmLogicTreeNode): def init(self, context): super(CanvasSetSliderNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') - self.add_input('NodeSocketFloat', 'Float') + self.add_input('ArmStringSocket', 'Element') + self.add_input('ArmFloatSocket', 'Float') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_text.py b/blender/arm/logicnode/canvas/LN_set_canvas_text.py index 598a4794..a706b31a 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_text.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_text.py @@ -9,7 +9,7 @@ class CanvasSetTextNode(ArmLogicTreeNode): def init(self, context): super(CanvasSetTextNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') - self.add_input('NodeSocketString', 'Text') + self.add_input('ArmStringSocket', 'Element') + self.add_input('ArmStringSocket', 'Text') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_text_color.py b/blender/arm/logicnode/canvas/LN_set_canvas_text_color.py index 2ac59b27..b6064691 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_text_color.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_text_color.py @@ -9,10 +9,10 @@ class CanvasSetTextColorNode(ArmLogicTreeNode): def init(self, context): super(CanvasSetTextColorNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') - self.add_input('NodeSocketFloat', 'R') - self.add_input('NodeSocketFloat', 'G') - self.add_input('NodeSocketFloat', 'B') - self.add_input('NodeSocketFloat', 'A') + self.add_input('ArmStringSocket', 'Element') + self.add_input('ArmFloatSocket', 'R') + self.add_input('ArmFloatSocket', 'G') + self.add_input('ArmFloatSocket', 'B') + self.add_input('ArmFloatSocket', 'A') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_visible.py b/blender/arm/logicnode/canvas/LN_set_canvas_visible.py index 8e8444f4..e9af8266 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_visible.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_visible.py @@ -9,7 +9,7 @@ class CanvasSetVisibleNode(ArmLogicTreeNode): def init(self, context): super(CanvasSetVisibleNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') - self.add_input('NodeSocketBool', 'Visible') + self.add_input('ArmStringSocket', 'Element') + self.add_input('ArmBoolSocket', 'Visible') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/deprecated/LN_get_mouse_lock.py b/blender/arm/logicnode/deprecated/LN_get_mouse_lock.py index 6bf63494..5a651247 100644 --- a/blender/arm/logicnode/deprecated/LN_get_mouse_lock.py +++ b/blender/arm/logicnode/deprecated/LN_get_mouse_lock.py @@ -16,7 +16,7 @@ class GetMouseLockNode(ArmLogicTreeNode): def init(self, context): super(GetMouseLockNode, self).init(context) - self.outputs.new('NodeSocketBool', 'Is Locked') + self.outputs.new('ArmBoolSocket', 'Is Locked') def get_replacement_node(self, node_tree: bpy.types.NodeTree): if self.arm_version not in (0, 1): diff --git a/blender/arm/logicnode/deprecated/LN_get_mouse_visible.py b/blender/arm/logicnode/deprecated/LN_get_mouse_visible.py index 0884b8eb..6f394deb 100644 --- a/blender/arm/logicnode/deprecated/LN_get_mouse_visible.py +++ b/blender/arm/logicnode/deprecated/LN_get_mouse_visible.py @@ -16,7 +16,7 @@ class GetMouseVisibleNode(ArmLogicTreeNode): def init(self, context): super(GetMouseVisibleNode, self).init(context) - self.outputs.new('NodeSocketBool', 'Is Visible') + self.outputs.new('ArmBoolSocket', 'Is Visible') def get_replacement_node(self, node_tree: bpy.types.NodeTree): if self.arm_version not in (0, 1): diff --git a/blender/arm/logicnode/deprecated/LN_mouse_coords.py b/blender/arm/logicnode/deprecated/LN_mouse_coords.py index df0ced8a..1244730d 100644 --- a/blender/arm/logicnode/deprecated/LN_mouse_coords.py +++ b/blender/arm/logicnode/deprecated/LN_mouse_coords.py @@ -13,9 +13,9 @@ class MouseCoordsNode(ArmLogicTreeNode): def init(self, context): super(MouseCoordsNode, self).init(context) - self.add_output('NodeSocketVector', 'Coords') - self.add_output('NodeSocketVector', 'Movement') - self.add_output('NodeSocketInt', 'Wheel') + self.add_output('ArmVectorSocket', 'Coords') + self.add_output('ArmVectorSocket', 'Movement') + self.add_output('ArmIntSocket', 'Wheel') def get_replacement_node(self, node_tree: bpy.types.NodeTree): if self.arm_version not in (0, 1): diff --git a/blender/arm/logicnode/deprecated/LN_on_gamepad.py b/blender/arm/logicnode/deprecated/LN_on_gamepad.py index 39433482..fe1f2913 100644 --- a/blender/arm/logicnode/deprecated/LN_on_gamepad.py +++ b/blender/arm/logicnode/deprecated/LN_on_gamepad.py @@ -45,7 +45,7 @@ class OnGamepadNode(ArmLogicTreeNode): def init(self, context): super(OnGamepadNode, self).init(context) self.add_output('ArmNodeSocketAction', 'Out') - self.add_input('NodeSocketInt', 'Gamepad') + self.add_input('ArmIntSocket', 'Gamepad') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/deprecated/LN_pause_trait.py b/blender/arm/logicnode/deprecated/LN_pause_trait.py index f5e921a7..7baa2d9f 100644 --- a/blender/arm/logicnode/deprecated/LN_pause_trait.py +++ b/blender/arm/logicnode/deprecated/LN_pause_trait.py @@ -13,5 +13,5 @@ class PauseTraitNode(ArmLogicTreeNode): def init(self, context): super(PauseTraitNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Trait') + self.add_input('ArmDynamicSocket', 'Trait') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/deprecated/LN_play_action.py b/blender/arm/logicnode/deprecated/LN_play_action.py index c914a0b0..a386d7f2 100644 --- a/blender/arm/logicnode/deprecated/LN_play_action.py +++ b/blender/arm/logicnode/deprecated/LN_play_action.py @@ -15,6 +15,6 @@ class PlayActionNode(ArmLogicTreeNode): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmNodeSocketAnimAction', 'Action') - self.add_input('NodeSocketFloat', 'Blend', default_value=0.2) + self.add_input('ArmFloatSocket', 'Blend', default_value=0.2) self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmNodeSocketAction', 'Done') diff --git a/blender/arm/logicnode/deprecated/LN_resume_trait.py b/blender/arm/logicnode/deprecated/LN_resume_trait.py index 0ea926e9..ccb9e183 100644 --- a/blender/arm/logicnode/deprecated/LN_resume_trait.py +++ b/blender/arm/logicnode/deprecated/LN_resume_trait.py @@ -13,5 +13,5 @@ class ResumeTraitNode(ArmLogicTreeNode): def init(self, context): super(ResumeTraitNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Trait') + self.add_input('ArmDynamicSocket', 'Trait') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/deprecated/LN_rotate_object_around_axis.py b/blender/arm/logicnode/deprecated/LN_rotate_object_around_axis.py index a1b2b5c4..49fe997d 100644 --- a/blender/arm/logicnode/deprecated/LN_rotate_object_around_axis.py +++ b/blender/arm/logicnode/deprecated/LN_rotate_object_around_axis.py @@ -15,8 +15,8 @@ class RotateObjectAroundAxisNode(ArmLogicTreeNode): super(RotateObjectAroundAxisNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketVector', 'Axis', default_value=[0, 0, 1]) - self.add_input('NodeSocketFloat', 'Angle') + self.add_input('ArmVectorSocket', 'Axis', default_value=[0, 0, 1]) + self.add_input('ArmFloatSocket', 'Angle') self.add_output('ArmNodeSocketAction', 'Out') def get_replacement_node(self, node_tree: bpy.types.NodeTree): diff --git a/blender/arm/logicnode/deprecated/LN_scale_object.py b/blender/arm/logicnode/deprecated/LN_scale_object.py index 4b3bb898..58f1078f 100644 --- a/blender/arm/logicnode/deprecated/LN_scale_object.py +++ b/blender/arm/logicnode/deprecated/LN_scale_object.py @@ -15,5 +15,5 @@ class ScaleObjectNode(ArmLogicTreeNode): super(ScaleObjectNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketVector', 'Scale') + self.add_input('ArmVectorSocket', 'Scale') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/deprecated/LN_set_mouse_lock.py b/blender/arm/logicnode/deprecated/LN_set_mouse_lock.py index 5612e87e..bb5ca4bb 100644 --- a/blender/arm/logicnode/deprecated/LN_set_mouse_lock.py +++ b/blender/arm/logicnode/deprecated/LN_set_mouse_lock.py @@ -14,7 +14,7 @@ class SetMouseLockNode(ArmLogicTreeNode): def init(self, context): super(SetMouseLockNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketBool', 'Lock') + self.add_input('ArmBoolSocket', 'Lock') self.add_output('ArmNodeSocketAction', 'Out') def get_replacement_node(self, node_tree: bpy.types.NodeTree): diff --git a/blender/arm/logicnode/deprecated/LN_set_mouse_visible.py b/blender/arm/logicnode/deprecated/LN_set_mouse_visible.py index c85738e6..19c29d1b 100644 --- a/blender/arm/logicnode/deprecated/LN_set_mouse_visible.py +++ b/blender/arm/logicnode/deprecated/LN_set_mouse_visible.py @@ -14,7 +14,7 @@ class ShowMouseNode(ArmLogicTreeNode): def init(self, context): super(ShowMouseNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketBool', 'Show') + self.add_input('ArmBoolSocket', 'Show') self.add_output('ArmNodeSocketAction', 'Out') def get_replacement_node(self, node_tree: bpy.types.NodeTree): diff --git a/blender/arm/logicnode/deprecated/LN_set_object_material.py b/blender/arm/logicnode/deprecated/LN_set_object_material.py index 25dc3f24..4ca6b967 100644 --- a/blender/arm/logicnode/deprecated/LN_set_object_material.py +++ b/blender/arm/logicnode/deprecated/LN_set_object_material.py @@ -14,5 +14,5 @@ class SetMaterialNode(ArmLogicTreeNode): super(SetMaterialNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketShader', 'Material') + self.add_input('ArmDynamicSocket', 'Material') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/deprecated/LN_surface_coords.py b/blender/arm/logicnode/deprecated/LN_surface_coords.py index dff2b872..480d4d95 100644 --- a/blender/arm/logicnode/deprecated/LN_surface_coords.py +++ b/blender/arm/logicnode/deprecated/LN_surface_coords.py @@ -14,5 +14,5 @@ class SurfaceCoordsNode(ArmLogicTreeNode): def init(self, context): super(SurfaceCoordsNode, self).init(context) - self.add_output('NodeSocketVector', 'Coords') - self.add_output('NodeSocketVector', 'Movement') + self.add_output('ArmVectorSocket', 'Coords') + self.add_output('ArmVectorSocket', 'Movement') diff --git a/blender/arm/logicnode/event/LN_on_timer.py b/blender/arm/logicnode/event/LN_on_timer.py index 209c0bc8..603755d2 100644 --- a/blender/arm/logicnode/event/LN_on_timer.py +++ b/blender/arm/logicnode/event/LN_on_timer.py @@ -11,7 +11,7 @@ class OnTimerNode(ArmLogicTreeNode): def init(self, context): super(OnTimerNode, self).init(context) - self.add_input('NodeSocketFloat', 'Duration') - self.add_input('NodeSocketBool', 'Repeat') + self.add_input('ArmFloatSocket', 'Duration') + self.add_input('ArmBoolSocket', 'Repeat') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/event/LN_send_event_to_object.py b/blender/arm/logicnode/event/LN_send_event_to_object.py index e9ebce12..03dd9ed1 100644 --- a/blender/arm/logicnode/event/LN_send_event_to_object.py +++ b/blender/arm/logicnode/event/LN_send_event_to_object.py @@ -16,7 +16,7 @@ class SendEventNode(ArmLogicTreeNode): def init(self, context): super(SendEventNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Event') + self.add_input('ArmStringSocket', 'Event') self.add_input('ArmNodeSocketObject', 'Object') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/event/LN_send_global_event.py b/blender/arm/logicnode/event/LN_send_global_event.py index a85b0b6d..25943b51 100644 --- a/blender/arm/logicnode/event/LN_send_global_event.py +++ b/blender/arm/logicnode/event/LN_send_global_event.py @@ -15,6 +15,6 @@ class SendGlobalEventNode(ArmLogicTreeNode): def init(self, context): super(SendGlobalEventNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Event') + self.add_input('ArmStringSocket', 'Event') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/input/LN_gamepad.py b/blender/arm/logicnode/input/LN_gamepad.py index df8dec2b..09c5cd0e 100644 --- a/blender/arm/logicnode/input/LN_gamepad.py +++ b/blender/arm/logicnode/input/LN_gamepad.py @@ -49,9 +49,9 @@ class GamepadNode(ArmLogicTreeNode): def init(self, context): super(GamepadNode, self).init(context) self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketBool', 'State') + self.add_output('ArmBoolSocket', 'State') - self.add_input('NodeSocketInt', 'Gamepad') + self.add_input('ArmIntSocket', 'Gamepad') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/input/LN_gamepad_coords.py b/blender/arm/logicnode/input/LN_gamepad_coords.py index 959db1e8..fdb61b75 100644 --- a/blender/arm/logicnode/input/LN_gamepad_coords.py +++ b/blender/arm/logicnode/input/LN_gamepad_coords.py @@ -13,11 +13,11 @@ class GamepadCoordsNode(ArmLogicTreeNode): def init(self, context): super(GamepadCoordsNode, self).init(context) - self.add_output('NodeSocketVector', 'Left Stick') - self.add_output('NodeSocketVector', 'Right Stick') - self.add_output('NodeSocketVector', 'Left Movement') - self.add_output('NodeSocketVector', 'Right Movement') - self.add_output('NodeSocketFloat', 'Left Trigger') - self.add_output('NodeSocketFloat', 'Right Trigger') + self.add_output('ArmVectorSocket', 'Left Stick') + self.add_output('ArmVectorSocket', 'Right Stick') + self.add_output('ArmVectorSocket', 'Left Movement') + self.add_output('ArmVectorSocket', 'Right Movement') + self.add_output('ArmFloatSocket', 'Left Trigger') + self.add_output('ArmFloatSocket', 'Right Trigger') - self.add_input('NodeSocketInt', 'Gamepad') + self.add_input('ArmIntSocket', 'Gamepad') diff --git a/blender/arm/logicnode/input/LN_get_cursor_location.py b/blender/arm/logicnode/input/LN_get_cursor_location.py index bc740be7..2d102343 100644 --- a/blender/arm/logicnode/input/LN_get_cursor_location.py +++ b/blender/arm/logicnode/input/LN_get_cursor_location.py @@ -9,7 +9,7 @@ class GetCursorLocationNode(ArmLogicTreeNode): def init(self, context): super(GetCursorLocationNode, self).init(context) - self.add_output('NodeSocketInt', 'X') - self.add_output('NodeSocketInt', 'Y') - self.add_output('NodeSocketInt', 'Inverted X') - self.add_output('NodeSocketInt', 'Inverted Y') + self.add_output('ArmIntSocket', 'X') + self.add_output('ArmIntSocket', 'Y') + self.add_output('ArmIntSocket', 'Inverted X') + self.add_output('ArmIntSocket', 'Inverted Y') diff --git a/blender/arm/logicnode/input/LN_get_cursor_state.py b/blender/arm/logicnode/input/LN_get_cursor_state.py index 221bf550..b09c253a 100644 --- a/blender/arm/logicnode/input/LN_get_cursor_state.py +++ b/blender/arm/logicnode/input/LN_get_cursor_state.py @@ -18,6 +18,6 @@ class GetCursorStateNode(ArmLogicTreeNode): def init(self, context): super(GetCursorStateNode, self).init(context) - self.outputs.new('NodeSocketBool', 'Is Hidden Locked') - self.outputs.new('NodeSocketBool', 'Is Hidden') - self.outputs.new('NodeSocketBool', 'Is Locked') + self.outputs.new('ArmBoolSocket', 'Is Hidden Locked') + self.outputs.new('ArmBoolSocket', 'Is Hidden') + self.outputs.new('ArmBoolSocket', 'Is Locked') diff --git a/blender/arm/logicnode/input/LN_get_mouse_movement.py b/blender/arm/logicnode/input/LN_get_mouse_movement.py index ee217b5c..a3ac08d2 100644 --- a/blender/arm/logicnode/input/LN_get_mouse_movement.py +++ b/blender/arm/logicnode/input/LN_get_mouse_movement.py @@ -12,13 +12,13 @@ class GetMouseMovementNode(ArmLogicTreeNode): def init(self, context): super(GetMouseMovementNode, self).init(context) - self.add_input('NodeSocketFloat', 'X Multiplier', default_value=-1.0) - self.add_input('NodeSocketFloat', 'Y Multiplier', default_value=-1.0) - self.add_input('NodeSocketFloat', 'Wheel Delta Multiplier', default_value=-1.0) + self.add_input('ArmFloatSocket', 'X Multiplier', default_value=-1.0) + self.add_input('ArmFloatSocket', 'Y Multiplier', default_value=-1.0) + self.add_input('ArmFloatSocket', 'Wheel Delta Multiplier', default_value=-1.0) - self.add_output('NodeSocketFloat', 'X') - self.add_output('NodeSocketFloat', 'Y') - self.add_output('NodeSocketFloat', 'Multiplied X') - self.add_output('NodeSocketFloat', 'Multiplied Y') - self.add_output('NodeSocketInt', 'Wheel Delta') - self.add_output('NodeSocketFloat', 'Multiplied Wheel Delta') + self.add_output('ArmFloatSocket', 'X') + self.add_output('ArmFloatSocket', 'Y') + self.add_output('ArmFloatSocket', 'Multiplied X') + self.add_output('ArmFloatSocket', 'Multiplied Y') + self.add_output('ArmIntSocket', 'Wheel Delta') + self.add_output('ArmFloatSocket', 'Multiplied Wheel Delta') diff --git a/blender/arm/logicnode/input/LN_get_touch_location.py b/blender/arm/logicnode/input/LN_get_touch_location.py index 88d6f6df..1acf6028 100644 --- a/blender/arm/logicnode/input/LN_get_touch_location.py +++ b/blender/arm/logicnode/input/LN_get_touch_location.py @@ -9,7 +9,7 @@ class GetTouchLocationNode(ArmLogicTreeNode): def init(self, context): super(GetTouchLocationNode, self).init(context) - self.add_output('NodeSocketInt', 'X') - self.add_output('NodeSocketInt', 'Y') - self.add_output('NodeSocketInt', 'Inverted X') - self.add_output('NodeSocketInt', 'Inverted Y') + self.add_output('ArmIntSocket', 'X') + self.add_output('ArmIntSocket', 'Y') + self.add_output('ArmIntSocket', 'Inverted X') + self.add_output('ArmIntSocket', 'Inverted Y') diff --git a/blender/arm/logicnode/input/LN_get_touch_movement.py b/blender/arm/logicnode/input/LN_get_touch_movement.py index 80f918f0..6c110679 100644 --- a/blender/arm/logicnode/input/LN_get_touch_movement.py +++ b/blender/arm/logicnode/input/LN_get_touch_movement.py @@ -9,10 +9,10 @@ class GetTouchMovementNode(ArmLogicTreeNode): def init(self, context): super(GetTouchMovementNode, self).init(context) - self.add_input('NodeSocketFloat', 'X Multiplier', default_value=-1.0) - self.add_input('NodeSocketFloat', 'Y Multiplier', default_value=-1.0) + self.add_input('ArmFloatSocket', 'X Multiplier', default_value=-1.0) + self.add_input('ArmFloatSocket', 'Y Multiplier', default_value=-1.0) - self.add_output('NodeSocketFloat', 'X') - self.add_output('NodeSocketFloat', 'Y') - self.add_output('NodeSocketFloat', 'Multiplied X') - self.add_output('NodeSocketFloat', 'Multiplied Y') + self.add_output('ArmFloatSocket', 'X') + self.add_output('ArmFloatSocket', 'Y') + self.add_output('ArmFloatSocket', 'Multiplied X') + self.add_output('ArmFloatSocket', 'Multiplied Y') diff --git a/blender/arm/logicnode/input/LN_keyboard.py b/blender/arm/logicnode/input/LN_keyboard.py index 230425db..b1613b02 100644 --- a/blender/arm/logicnode/input/LN_keyboard.py +++ b/blender/arm/logicnode/input/LN_keyboard.py @@ -73,7 +73,7 @@ class KeyboardNode(ArmLogicTreeNode): def init(self, context): super(KeyboardNode, self).init(context) self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketBool', 'State') + self.add_output('ArmBoolSocket', 'State') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/input/LN_mouse.py b/blender/arm/logicnode/input/LN_mouse.py index 3152a132..69663544 100644 --- a/blender/arm/logicnode/input/LN_mouse.py +++ b/blender/arm/logicnode/input/LN_mouse.py @@ -24,7 +24,7 @@ class MouseNode(ArmLogicTreeNode): def init(self, context): super(MouseNode, self).init(context) self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketBool', 'State') + self.add_output('ArmBoolSocket', 'State') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/input/LN_on_swipe.py b/blender/arm/logicnode/input/LN_on_swipe.py index ed104a41..61c11913 100644 --- a/blender/arm/logicnode/input/LN_on_swipe.py +++ b/blender/arm/logicnode/input/LN_on_swipe.py @@ -7,7 +7,7 @@ class NodeAddOutputButton(bpy.types.Operator): bl_label = 'Add 4 States' bl_options = {'UNDO', 'INTERNAL'} node_index: StringProperty(name='Node Index', default='') - socket_type: StringProperty(name='Socket Type', default='NodeSocketShader') + socket_type: StringProperty(name='Socket Type', default='ArmDynamicSocket') name_format: StringProperty(name='Name Format', default='Output {0}') index_name_offset: IntProperty(name='Index Name Offset', default=0) @@ -21,10 +21,10 @@ class NodeAddOutputButton(bpy.types.Operator): global array_nodes node = array_nodes[self.node_index] outs = node.outputs - outs.new('NodeSocketBool', self.get_name_state(len(outs), node.min_outputs)) - outs.new('NodeSocketBool', self.get_name_state(len(outs), node.min_outputs)) - outs.new('NodeSocketBool', self.get_name_state(len(outs), node.min_outputs)) - outs.new('NodeSocketBool', self.get_name_state(len(outs), node.min_outputs)) + outs.new('ArmBoolSocket', self.get_name_state(len(outs), node.min_outputs)) + outs.new('ArmBoolSocket', self.get_name_state(len(outs), node.min_outputs)) + outs.new('ArmBoolSocket', self.get_name_state(len(outs), node.min_outputs)) + outs.new('ArmBoolSocket', self.get_name_state(len(outs), node.min_outputs)) return{'FINISHED'} # Custom class for remove output parameters (in 4 directions) @@ -61,14 +61,14 @@ class OnSwipeNode(ArmLogicTreeNode): def init(self, context): super(OnSwipeNode, self).init(context) - self.inputs.new('NodeSocketFloat', 'Time') + self.inputs.new('ArmFloatSocket', 'Time') self.inputs[-1].default_value = 0.15 - self.inputs.new('NodeSocketInt', 'Min Length (px)') + self.inputs.new('ArmIntSocket', 'Min Length (px)') self.inputs[-1].default_value = 100 self.outputs.new('ArmNodeSocketAction', 'Out') - self.outputs.new('NodeSocketVector', 'Direction') - self.outputs.new('NodeSocketInt', 'Length (px)') - self.outputs.new('NodeSocketInt', 'Angle (0-360)') + self.outputs.new('ArmVectorSocket', 'Direction') + self.outputs.new('ArmIntSocket', 'Length (px)') + self.outputs.new('ArmIntSocket', 'Angle (0-360)') # Draw node buttons def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/input/LN_on_tap_screen.py b/blender/arm/logicnode/input/LN_on_tap_screen.py index 9d27e05c..c0450ca0 100644 --- a/blender/arm/logicnode/input/LN_on_tap_screen.py +++ b/blender/arm/logicnode/input/LN_on_tap_screen.py @@ -20,15 +20,15 @@ class OnTapScreen(ArmLogicTreeNode): def init(self, context): super(OnTapScreen, self).init(context) - self.add_input('NodeSocketFloat', 'Duration') + self.add_input('ArmFloatSocket', 'Duration') self.inputs[-1].default_value = 0.3 - self.add_input('NodeSocketFloat', 'Interval') + self.add_input('ArmFloatSocket', 'Interval') self.inputs[-1].default_value = 0.0 - self.add_input('NodeSocketInt', 'Repeat') + self.add_input('ArmIntSocket', 'Repeat') self.inputs[-1].default_value = 2 self.add_output('ArmNodeSocketAction', 'Done') self.add_output('ArmNodeSocketAction', 'Fail') self.add_output('ArmNodeSocketAction', 'Tap') - self.add_output('NodeSocketInt', 'Tap Number') - self.add_output('NodeSocketVector', 'Coords') + self.add_output('ArmIntSocket', 'Tap Number') + self.add_output('ArmVectorSocket', 'Coords') diff --git a/blender/arm/logicnode/input/LN_sensor_coords.py b/blender/arm/logicnode/input/LN_sensor_coords.py index 9687630e..439f8eb1 100644 --- a/blender/arm/logicnode/input/LN_sensor_coords.py +++ b/blender/arm/logicnode/input/LN_sensor_coords.py @@ -9,4 +9,4 @@ class SensorCoordsNode(ArmLogicTreeNode): def init(self, context): super(SensorCoordsNode, self).init(context) - self.add_output('NodeSocketVector', 'Coords') + self.add_output('ArmVectorSocket', 'Coords') diff --git a/blender/arm/logicnode/input/LN_set_cursor_state.py b/blender/arm/logicnode/input/LN_set_cursor_state.py index 15f280cb..c1f38fde 100644 --- a/blender/arm/logicnode/input/LN_set_cursor_state.py +++ b/blender/arm/logicnode/input/LN_set_cursor_state.py @@ -25,7 +25,7 @@ class SetCursorStateNode(ArmLogicTreeNode): def init(self, context): super(SetCursorStateNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketBool', 'State') + self.add_input('ArmBoolSocket', 'State') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/input/LN_touch.py b/blender/arm/logicnode/input/LN_touch.py index 2beb6ac1..ffbc32c6 100644 --- a/blender/arm/logicnode/input/LN_touch.py +++ b/blender/arm/logicnode/input/LN_touch.py @@ -18,7 +18,7 @@ class SurfaceNode(ArmLogicTreeNode): def init(self, context): super(SurfaceNode, self).init(context) self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketBool', 'State') + self.add_output('ArmBoolSocket', 'State') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/input/LN_virtual_button.py b/blender/arm/logicnode/input/LN_virtual_button.py index 9d4c9c3f..28a52189 100644 --- a/blender/arm/logicnode/input/LN_virtual_button.py +++ b/blender/arm/logicnode/input/LN_virtual_button.py @@ -18,7 +18,7 @@ class VirtualButtonNode(ArmLogicTreeNode): def init(self, context): super(VirtualButtonNode, self).init(context) self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketBool', 'State') + self.add_output('ArmBoolSocket', 'State') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/light/LN_set_light_color.py b/blender/arm/logicnode/light/LN_set_light_color.py index a62d8366..38b3d6ef 100644 --- a/blender/arm/logicnode/light/LN_set_light_color.py +++ b/blender/arm/logicnode/light/LN_set_light_color.py @@ -10,6 +10,6 @@ class SetLightColorNode(ArmLogicTreeNode): super(SetLightColorNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Light') - self.add_input('NodeSocketColor', 'Color', default_value=[1.0, 1.0, 1.0, 1.0]) + self.add_input('ArmColorSocket', 'Color', default_value=[1.0, 1.0, 1.0, 1.0]) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/light/LN_set_light_strength.py b/blender/arm/logicnode/light/LN_set_light_strength.py index cd04b753..cbf11700 100644 --- a/blender/arm/logicnode/light/LN_set_light_strength.py +++ b/blender/arm/logicnode/light/LN_set_light_strength.py @@ -10,6 +10,6 @@ class SetLightStrengthNode(ArmLogicTreeNode): super(SetLightStrengthNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Light') - self.add_input('NodeSocketFloat', 'Strength', default_value=250) + self.add_input('ArmFloatSocket', 'Strength', default_value=250) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/logic/LN_branch.py b/blender/arm/logicnode/logic/LN_branch.py index 45037e1d..d7b5536d 100644 --- a/blender/arm/logicnode/logic/LN_branch.py +++ b/blender/arm/logicnode/logic/LN_branch.py @@ -11,7 +11,7 @@ class BranchNode(ArmLogicTreeNode): def init(self, context): super(BranchNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketBool', 'Bool') + self.add_input('ArmBoolSocket', 'Bool') self.add_output('ArmNodeSocketAction', 'True') self.add_output('ArmNodeSocketAction', 'False') diff --git a/blender/arm/logicnode/logic/LN_call_function.py b/blender/arm/logicnode/logic/LN_call_function.py index 0dce0371..20808c71 100644 --- a/blender/arm/logicnode/logic/LN_call_function.py +++ b/blender/arm/logicnode/logic/LN_call_function.py @@ -16,17 +16,17 @@ class CallFunctionNode(ArmLogicTreeNode): def init(self, context): super(CallFunctionNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Trait/Any') - self.add_input('NodeSocketString', 'Function') + self.add_input('ArmDynamicSocket', 'Trait/Any') + self.add_input('ArmStringSocket', 'Function') self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketShader', 'Result') + self.add_output('ArmDynamicSocket', 'Result') def draw_buttons(self, context, layout): row = layout.row(align=True) op = row.operator('arm.node_add_input', text='Add Arg', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketShader' + op.socket_type = 'ArmDynamicSocket' op.name_format = "Arg {0}" op.index_name_offset = -2 op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True) diff --git a/blender/arm/logicnode/logic/LN_function.py b/blender/arm/logicnode/logic/LN_function.py index ee2ecc7d..5af1e5e7 100644 --- a/blender/arm/logicnode/logic/LN_function.py +++ b/blender/arm/logicnode/logic/LN_function.py @@ -27,7 +27,7 @@ class FunctionNode(ArmLogicTreeNode): row = layout.row(align=True) op = row.operator('arm.node_add_output', text='Add Arg', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketShader' + op.socket_type = 'ArmDynamicSocket' op.name_format = "Arg {0}" op.index_name_offset = 0 op2 = row.operator('arm.node_remove_output', text='', icon='X', emboss=True) diff --git a/blender/arm/logicnode/logic/LN_function_output.py b/blender/arm/logicnode/logic/LN_function_output.py index 6ac65e7c..87343512 100644 --- a/blender/arm/logicnode/logic/LN_function_output.py +++ b/blender/arm/logicnode/logic/LN_function_output.py @@ -13,7 +13,7 @@ class FunctionOutputNode(ArmLogicTreeNode): def init(self, context): super(FunctionOutputNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmDynamicSocket', 'Value') function_name: StringProperty(name="Name") diff --git a/blender/arm/logicnode/logic/LN_gate.py b/blender/arm/logicnode/logic/LN_gate.py index ad931f7c..286c6580 100644 --- a/blender/arm/logicnode/logic/LN_gate.py +++ b/blender/arm/logicnode/logic/LN_gate.py @@ -41,8 +41,8 @@ class GateNode(ArmLogicTreeNode): def init(self, context): super(GateNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Input 1') - self.add_input('NodeSocketShader', 'Input 2') + self.add_input('ArmDynamicSocket', 'Input 1') + self.add_input('ArmDynamicSocket', 'Input 2') self.add_output('ArmNodeSocketAction', 'True') self.add_output('ArmNodeSocketAction', 'False') @@ -57,6 +57,6 @@ class GateNode(ArmLogicTreeNode): row = layout.row(align=True) op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketShader' + op.socket_type = 'ArmDynamicSocket' op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True) op2.node_index = str(id(self)) diff --git a/blender/arm/logicnode/logic/LN_invert_boolean.py b/blender/arm/logicnode/logic/LN_invert_boolean.py index 924ab58e..5c5475f1 100644 --- a/blender/arm/logicnode/logic/LN_invert_boolean.py +++ b/blender/arm/logicnode/logic/LN_invert_boolean.py @@ -8,6 +8,6 @@ class NotNode(ArmLogicTreeNode): def init(self, context): super(NotNode, self).init(context) - self.add_input('NodeSocketBool', 'Bool In') + self.add_input('ArmBoolSocket', 'Bool In') - self.add_output('NodeSocketBool', 'Bool Out') + self.add_output('ArmBoolSocket', 'Bool Out') diff --git a/blender/arm/logicnode/logic/LN_is_false.py b/blender/arm/logicnode/logic/LN_is_false.py index 8eb86772..a4ec30df 100644 --- a/blender/arm/logicnode/logic/LN_is_false.py +++ b/blender/arm/logicnode/logic/LN_is_false.py @@ -13,6 +13,6 @@ class IsFalseNode(ArmLogicTreeNode): def init(self, context): super(IsFalseNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketBool', 'Bool') + self.add_input('ArmBoolSocket', 'Bool') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/logic/LN_is_not_null.py b/blender/arm/logicnode/logic/LN_is_not_null.py index 4e7e0cb4..200b4cdf 100644 --- a/blender/arm/logicnode/logic/LN_is_not_null.py +++ b/blender/arm/logicnode/logic/LN_is_not_null.py @@ -12,6 +12,6 @@ class IsNotNoneNode(ArmLogicTreeNode): def init(self, context): super(IsNotNoneNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmDynamicSocket', 'Value') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/logic/LN_is_null.py b/blender/arm/logicnode/logic/LN_is_null.py index b8f77a80..a08e2c32 100644 --- a/blender/arm/logicnode/logic/LN_is_null.py +++ b/blender/arm/logicnode/logic/LN_is_null.py @@ -13,6 +13,6 @@ class IsNoneNode(ArmLogicTreeNode): def init(self, context): super(IsNoneNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmDynamicSocket', 'Value') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/logic/LN_is_true.py b/blender/arm/logicnode/logic/LN_is_true.py index 21f255d0..6e88f6b8 100644 --- a/blender/arm/logicnode/logic/LN_is_true.py +++ b/blender/arm/logicnode/logic/LN_is_true.py @@ -12,6 +12,6 @@ class IsTrueNode(ArmLogicTreeNode): def init(self, context): super(IsTrueNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketBool', 'Bool') + self.add_input('ArmBoolSocket', 'Bool') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/logic/LN_loop.py b/blender/arm/logicnode/logic/LN_loop.py index 9d69085e..d6ef4b0e 100644 --- a/blender/arm/logicnode/logic/LN_loop.py +++ b/blender/arm/logicnode/logic/LN_loop.py @@ -23,11 +23,11 @@ class LoopNode(ArmLogicTreeNode): def init(self, context): super(LoopNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketInt', 'From') - self.add_input('NodeSocketInt', 'To') + self.add_input('ArmIntSocket', 'From') + self.add_input('ArmIntSocket', 'To') self.add_output('ArmNodeSocketAction', 'Loop') - self.add_output('NodeSocketInt', 'Index') + self.add_output('ArmIntSocket', 'Index') self.add_output('ArmNodeSocketAction', 'Done') def draw_label(self) -> str: diff --git a/blender/arm/logicnode/logic/LN_null.py b/blender/arm/logicnode/logic/LN_null.py index 610328a1..7ed6a18f 100644 --- a/blender/arm/logicnode/logic/LN_null.py +++ b/blender/arm/logicnode/logic/LN_null.py @@ -8,4 +8,4 @@ class NoneNode(ArmLogicTreeNode): def init(self, context): super(NoneNode, self).init(context) - self.add_output('NodeSocketShader', 'Null') + self.add_output('ArmDynamicSocket', 'Null') diff --git a/blender/arm/logicnode/logic/LN_output_to_boolean.py b/blender/arm/logicnode/logic/LN_output_to_boolean.py index f7ed282c..e8b040db 100644 --- a/blender/arm/logicnode/logic/LN_output_to_boolean.py +++ b/blender/arm/logicnode/logic/LN_output_to_boolean.py @@ -11,4 +11,4 @@ class ToBoolNode(ArmLogicTreeNode): super(ToBoolNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_output('NodeSocketBool', 'Bool') + self.add_output('ArmBoolSocket', 'Bool') diff --git a/blender/arm/logicnode/logic/LN_switch_output.py b/blender/arm/logicnode/logic/LN_switch_output.py index 95a9ddba..26bd5246 100644 --- a/blender/arm/logicnode/logic/LN_switch_output.py +++ b/blender/arm/logicnode/logic/LN_switch_output.py @@ -18,7 +18,7 @@ class SwitchNode(ArmLogicTreeNode): def init(self, context): super(SwitchNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmDynamicSocket', 'Value') self.add_output('ArmNodeSocketAction', 'Default') @@ -26,7 +26,7 @@ class SwitchNode(ArmLogicTreeNode): row = layout.row(align=True) op = row.operator('arm.node_add_input_output', text='New', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.in_socket_type = 'NodeSocketShader' + op.in_socket_type = 'ArmDynamicSocket' op.out_socket_type = 'ArmNodeSocketAction' op.in_name_format = 'Case {0}' op.out_name_format = 'Case {0}' diff --git a/blender/arm/logicnode/logic/LN_value_changed.py b/blender/arm/logicnode/logic/LN_value_changed.py index 42b9ae0a..c805d3a1 100644 --- a/blender/arm/logicnode/logic/LN_value_changed.py +++ b/blender/arm/logicnode/logic/LN_value_changed.py @@ -9,7 +9,7 @@ class ValueChangedNode(ArmLogicTreeNode): def init(self, context): super(ValueChangedNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmDynamicSocket', 'Value') self.add_output('ArmNodeSocketAction', 'Changed') self.add_output('ArmNodeSocketAction', 'Unchanged') diff --git a/blender/arm/logicnode/logic/LN_while_true.py b/blender/arm/logicnode/logic/LN_while_true.py index ba99b9ad..f817c1fd 100644 --- a/blender/arm/logicnode/logic/LN_while_true.py +++ b/blender/arm/logicnode/logic/LN_while_true.py @@ -18,7 +18,7 @@ class WhileNode(ArmLogicTreeNode): def init(self, context): super(WhileNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketBool', 'Condition') + self.add_input('ArmBoolSocket', 'Condition') self.add_output('ArmNodeSocketAction', 'Loop') self.add_output('ArmNodeSocketAction', 'Done') diff --git a/blender/arm/logicnode/material/LN_get_object_material.py b/blender/arm/logicnode/material/LN_get_object_material.py index 864c48ee..ec560fcf 100644 --- a/blender/arm/logicnode/material/LN_get_object_material.py +++ b/blender/arm/logicnode/material/LN_get_object_material.py @@ -9,6 +9,6 @@ class GetMaterialNode(ArmLogicTreeNode): def init(self, context): super(GetMaterialNode, self).init(context) self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketInt', 'Slot') + self.add_input('ArmIntSocket', 'Slot') - self.add_output('NodeSocketShader', 'Material') + self.add_output('ArmDynamicSocket', 'Material') diff --git a/blender/arm/logicnode/material/LN_material.py b/blender/arm/logicnode/material/LN_material.py index 7b3a4cc0..63cf9c46 100644 --- a/blender/arm/logicnode/material/LN_material.py +++ b/blender/arm/logicnode/material/LN_material.py @@ -22,7 +22,7 @@ class MaterialNode(ArmLogicTreeNode): def init(self, context): super(MaterialNode, self).init(context) - self.add_output('NodeSocketShader', 'Material', is_var=True) + self.add_output('ArmDynamicSocket', 'Material', is_var=True) def draw_buttons(self, context, layout): layout.prop_search(self, 'property0', bpy.data, 'materials', icon='NONE', text='') diff --git a/blender/arm/logicnode/material/LN_set_material_image_param.py b/blender/arm/logicnode/material/LN_set_material_image_param.py index 777794c2..e6188f76 100644 --- a/blender/arm/logicnode/material/LN_set_material_image_param.py +++ b/blender/arm/logicnode/material/LN_set_material_image_param.py @@ -10,8 +10,8 @@ class SetMaterialImageParamNode(ArmLogicTreeNode): def init(self, context): super(SetMaterialImageParamNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Material') - self.add_input('NodeSocketString', 'Node') - self.add_input('NodeSocketString', 'Image') + self.add_input('ArmDynamicSocket', 'Material') + self.add_input('ArmStringSocket', 'Node') + self.add_input('ArmStringSocket', 'Image') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/material/LN_set_material_rgb_param.py b/blender/arm/logicnode/material/LN_set_material_rgb_param.py index 34948c9d..1b826834 100644 --- a/blender/arm/logicnode/material/LN_set_material_rgb_param.py +++ b/blender/arm/logicnode/material/LN_set_material_rgb_param.py @@ -10,8 +10,8 @@ class SetMaterialRgbParamNode(ArmLogicTreeNode): def init(self, context): super(SetMaterialRgbParamNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Material') - self.add_input('NodeSocketString', 'Node') - self.add_input('NodeSocketColor', 'Color') + self.add_input('ArmDynamicSocket', 'Material') + self.add_input('ArmStringSocket', 'Node') + self.add_input('ArmColorSocket', 'Color') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/material/LN_set_material_value_param.py b/blender/arm/logicnode/material/LN_set_material_value_param.py index 426807d1..afae0aa7 100644 --- a/blender/arm/logicnode/material/LN_set_material_value_param.py +++ b/blender/arm/logicnode/material/LN_set_material_value_param.py @@ -10,8 +10,8 @@ class SetMaterialValueParamNode(ArmLogicTreeNode): def init(self, context): super(SetMaterialValueParamNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Material') - self.add_input('NodeSocketString', 'Node') - self.add_input('NodeSocketFloat', 'Float') + self.add_input('ArmDynamicSocket', 'Material') + self.add_input('ArmStringSocket', 'Node') + self.add_input('ArmFloatSocket', 'Float') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/material/LN_set_object_material_slot.py b/blender/arm/logicnode/material/LN_set_object_material_slot.py index 3806c898..acfa6a68 100644 --- a/blender/arm/logicnode/material/LN_set_object_material_slot.py +++ b/blender/arm/logicnode/material/LN_set_object_material_slot.py @@ -10,7 +10,7 @@ class SetMaterialSlotNode(ArmLogicTreeNode): super(SetMaterialSlotNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketShader', 'Material') - self.add_input('NodeSocketInt', 'Slot') + self.add_input('ArmDynamicSocket', 'Material') + self.add_input('ArmIntSocket', 'Slot') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/math/LN_clamp.py b/blender/arm/logicnode/math/LN_clamp.py index 9205d4d9..8337dd97 100644 --- a/blender/arm/logicnode/math/LN_clamp.py +++ b/blender/arm/logicnode/math/LN_clamp.py @@ -11,8 +11,8 @@ class ClampNode(ArmLogicTreeNode): def init(self, context): super(ClampNode, self).init(context) - self.add_input('NodeSocketFloat', 'Value') - self.add_input('NodeSocketFloat', 'Min') - self.add_input('NodeSocketFloat', 'Max') + self.add_input('ArmFloatSocket', 'Value') + self.add_input('ArmFloatSocket', 'Min') + self.add_input('ArmFloatSocket', 'Max') - self.add_output('NodeSocketFloat', 'Result') + self.add_output('ArmFloatSocket', 'Result') diff --git a/blender/arm/logicnode/math/LN_compare.py b/blender/arm/logicnode/math/LN_compare.py index 3081376d..027ee9ed 100644 --- a/blender/arm/logicnode/math/LN_compare.py +++ b/blender/arm/logicnode/math/LN_compare.py @@ -31,10 +31,10 @@ class CompareNode(ArmLogicTreeNode): def init(self, context): super(CompareNode, self).init(context) - self.add_input('NodeSocketShader', 'Value') - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmDynamicSocket', 'Value') + self.add_input('ArmDynamicSocket', 'Value') - self.add_output('NodeSocketBool', 'Bool') + self.add_output('ArmBoolSocket', 'Bool') def draw_buttons(self, context, layout): layout.prop(self, 'property0') @@ -46,6 +46,6 @@ class CompareNode(ArmLogicTreeNode): row = layout.row(align=True) op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketShader' + op.socket_type = 'ArmDynamicSocket' op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True) op2.node_index = str(id(self)) diff --git a/blender/arm/logicnode/math/LN_deg_to_rad.py b/blender/arm/logicnode/math/LN_deg_to_rad.py index 64e63a61..034e73e9 100644 --- a/blender/arm/logicnode/math/LN_deg_to_rad.py +++ b/blender/arm/logicnode/math/LN_deg_to_rad.py @@ -9,6 +9,6 @@ class DegToRadNode(ArmLogicTreeNode): def init(self, context): super(DegToRadNode, self).init(context) - self.add_input('NodeSocketFloat', 'Degrees') + self.add_input('ArmFloatSocket', 'Degrees') - self.add_output('NodeSocketFloat', 'Radians') + self.add_output('ArmFloatSocket', 'Radians') diff --git a/blender/arm/logicnode/math/LN_map_range.py b/blender/arm/logicnode/math/LN_map_range.py index d1d370b8..29de5d67 100644 --- a/blender/arm/logicnode/math/LN_map_range.py +++ b/blender/arm/logicnode/math/LN_map_range.py @@ -11,10 +11,10 @@ class MapRangeNode(ArmLogicTreeNode): def init(self, context): super(MapRangeNode, self).init(context) - self.add_input('NodeSocketFloat', 'Value', default_value=1.0) - self.add_input('NodeSocketFloat', 'From Min') - self.add_input('NodeSocketFloat', 'From Max', default_value=1.0) - self.add_input('NodeSocketFloat', 'To Min') - self.add_input('NodeSocketFloat', 'To Max', default_value=1.0) + self.add_input('ArmFloatSocket', 'Value', default_value=1.0) + self.add_input('ArmFloatSocket', 'From Min') + self.add_input('ArmFloatSocket', 'From Max', default_value=1.0) + self.add_input('ArmFloatSocket', 'To Min') + self.add_input('ArmFloatSocket', 'To Max', default_value=1.0) - self.add_output('NodeSocketFloat', 'Result') + self.add_output('ArmFloatSocket', 'Result') diff --git a/blender/arm/logicnode/math/LN_math.py b/blender/arm/logicnode/math/LN_math.py index e090811f..ddae6d7f 100644 --- a/blender/arm/logicnode/math/LN_math.py +++ b/blender/arm/logicnode/math/LN_math.py @@ -51,13 +51,13 @@ class MathNode(ArmLogicTreeNode): # Many arguments: Add, Subtract, Multiply, Divide if (self.get_count_in(select_current) == 0): while (len(self.inputs) < 2): - self.add_input('NodeSocketFloat', 'Value ' + str(len(self.inputs))) + self.add_input('ArmFloatSocket', 'Value ' + str(len(self.inputs))) # 2 arguments: Max, Min, Power, Arctan2, Modulo, Less Than, Greater Than if (self.get_count_in(select_current) == 2): while (len(self.inputs) > 2): self.inputs.remove(self.inputs.values()[-1]) while (len(self.inputs) < 2): - self.add_input('NodeSocketFloat', 'Value ' + str(len(self.inputs))) + self.add_input('ArmFloatSocket', 'Value ' + str(len(self.inputs))) # 1 argument: Sine, Cosine, Abs, Tangent, Arcsine, Arccosine, Arctangent, Logarithm, Round, Floor, Ceil, Square Root, Fract, Exponent if (self.get_count_in(select_current) == 1): while (len(self.inputs) > 1): @@ -100,10 +100,10 @@ class MathNode(ArmLogicTreeNode): def init(self, context): super(MathNode, self).init(context) - self.add_input('NodeSocketFloat', 'Value 0', default_value=0.0) - self.add_input('NodeSocketFloat', 'Value 1', default_value=0.0) + self.add_input('ArmFloatSocket', 'Value 0', default_value=0.0) + self.add_input('ArmFloatSocket', 'Value 1', default_value=0.0) - self.add_output('NodeSocketFloat', 'Result') + self.add_output('ArmFloatSocket', 'Result') def draw_buttons(self, context, layout): layout.prop(self, 'property1') @@ -114,7 +114,7 @@ class MathNode(ArmLogicTreeNode): column = row.column(align=True) op = column.operator('arm.node_add_input', text='Add Value', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketFloat' + op.socket_type = 'ArmFloatSocket' op.name_format = 'Value {0}' column = row.column(align=True) op = column.operator('arm.node_remove_input', text='', icon='X', emboss=True) diff --git a/blender/arm/logicnode/math/LN_math_expression.py b/blender/arm/logicnode/math/LN_math_expression.py index 125d3e77..4018b3e1 100644 --- a/blender/arm/logicnode/math/LN_math_expression.py +++ b/blender/arm/logicnode/math/LN_math_expression.py @@ -163,9 +163,9 @@ class MathExpressionNode(ArmLogicTreeNode): def init(self, context): super(MathExpressionNode, self).init(context) - self.add_input('NodeSocketFloat', self.get_variable_name(0), default_value=0.0) - self.add_input('NodeSocketFloat', self.get_variable_name(1), default_value=0.0) - self.add_output('NodeSocketFloat', 'Result') + self.add_input('ArmFloatSocket', self.get_variable_name(0), default_value=0.0) + self.add_input('ArmFloatSocket', self.get_variable_name(1), default_value=0.0) + self.add_output('ArmFloatSocket', 'Result') def draw_buttons(self, context, layout): layout.prop(self, 'property1') @@ -181,7 +181,7 @@ class MathExpressionNode(ArmLogicTreeNode): if len(self.inputs) == 10: column.enabled = False op.node_index = str(id(self)) - op.socket_type = 'NodeSocketFloat' + op.socket_type = 'ArmFloatSocket' op.name_format = self.get_variable_name(len(self.inputs)) column = row.column(align=True) op = column.operator('arm.node_remove_input', text='', icon='X', emboss=True) diff --git a/blender/arm/logicnode/math/LN_matrix_math.py b/blender/arm/logicnode/math/LN_matrix_math.py index 4a05ce3d..903a4e87 100644 --- a/blender/arm/logicnode/math/LN_matrix_math.py +++ b/blender/arm/logicnode/math/LN_matrix_math.py @@ -14,10 +14,10 @@ class MatrixMathNode(ArmLogicTreeNode): def init(self, context): super(MatrixMathNode, self).init(context) - self.add_input('NodeSocketShader', 'Matrix 1') - self.add_input('NodeSocketShader', 'Matrix 2') + self.add_input('ArmDynamicSocket', 'Matrix 1') + self.add_input('ArmDynamicSocket', 'Matrix 2') - self.add_output('NodeSocketShader', 'Result') + self.add_output('ArmDynamicSocket', 'Result') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/math/LN_mix.py b/blender/arm/logicnode/math/LN_mix.py index 92fa80df..0d4a98e3 100644 --- a/blender/arm/logicnode/math/LN_mix.py +++ b/blender/arm/logicnode/math/LN_mix.py @@ -32,11 +32,11 @@ class MixNode(ArmLogicTreeNode): def init(self, context): super(MixNode, self).init(context) - self.add_input('NodeSocketFloat', 'Factor', default_value=0.0) - self.add_input('NodeSocketFloat', 'Value 1', default_value=0.0) - self.add_input('NodeSocketFloat', 'Value 2', default_value=1.0) + self.add_input('ArmFloatSocket', 'Factor', default_value=0.0) + self.add_input('ArmFloatSocket', 'Value 1', default_value=0.0) + self.add_input('ArmFloatSocket', 'Value 2', default_value=1.0) - self.add_output('NodeSocketFloat', 'Result') + self.add_output('ArmFloatSocket', 'Result') def draw_buttons(self, context, layout): layout.prop(self, 'property2') diff --git a/blender/arm/logicnode/math/LN_mix_vector.py b/blender/arm/logicnode/math/LN_mix_vector.py index cbab381b..9084d75c 100644 --- a/blender/arm/logicnode/math/LN_mix_vector.py +++ b/blender/arm/logicnode/math/LN_mix_vector.py @@ -34,11 +34,11 @@ class VectorMixNode(ArmLogicTreeNode): def init(self, context): super(VectorMixNode, self).init(context) - self.add_input('NodeSocketFloat', 'Factor', default_value=0.0) - self.add_input('NodeSocketVector', 'Vector 1', default_value=[0.0, 0.0, 0.0]) - self.add_input('NodeSocketVector', 'Vector 2', default_value=[1.0, 1.0, 1.0]) + self.add_input('ArmFloatSocket', 'Factor', default_value=0.0) + self.add_input('ArmVectorSocket', 'Vector 1', default_value=[0.0, 0.0, 0.0]) + self.add_input('ArmVectorSocket', 'Vector 2', default_value=[1.0, 1.0, 1.0]) - self.add_output('NodeSocketVector', 'Result') + self.add_output('ArmVectorSocket', 'Result') def draw_buttons(self, context, layout): layout.prop(self, 'property2') diff --git a/blender/arm/logicnode/math/LN_quaternion_math.py b/blender/arm/logicnode/math/LN_quaternion_math.py index bcaa7753..8dfdc590 100644 --- a/blender/arm/logicnode/math/LN_quaternion_math.py +++ b/blender/arm/logicnode/math/LN_quaternion_math.py @@ -15,16 +15,16 @@ class QuaternionMathNode(ArmLogicTreeNode): if value: if ((self.property0 == 'Module') or (self.property0 == 'DotProduct') or (self.property0 == 'ToAxisAngle')) and (len(self.outputs) > 1): self.outputs.remove(self.outputs.values()[-1]) # Module/DotProduct/ToAxisAngle - self.add_output('NodeSocketFloat', 'X') # Result X - self.add_output('NodeSocketFloat', 'Y') # Result Y - self.add_output('NodeSocketFloat', 'Z') # Result Z - self.add_output('NodeSocketFloat', 'W') # Result W + self.add_output('ArmFloatSocket', 'X') # Result X + self.add_output('ArmFloatSocket', 'Y') # Result Y + self.add_output('ArmFloatSocket', 'Z') # Result Z + self.add_output('ArmFloatSocket', 'W') # Result W if (self.property0 == 'Module'): - self.add_output('NodeSocketFloat', 'Module') # Module + self.add_output('ArmFloatSocket', 'Module') # Module if (self.property0 == 'DotProduct'): - self.add_output('NodeSocketFloat', 'Scalar') # DotProduct + self.add_output('ArmFloatSocket', 'Scalar') # DotProduct if (self.property0 == 'ToAxisAngle'): - self.add_output('NodeSocketFloat', 'To Axis Angle') # ToAxisAngle + self.add_output('ArmFloatSocket', 'To Axis Angle') # ToAxisAngle else: if ((self.property0 == 'Module') or (self.property0 == 'DotProduct') or (self.property0 == 'ToAxisAngle')) and (len(self.outputs) > 1): self.outputs.remove(self.outputs.values()[-1]) # Module/DotProduct/ToAxisAngle @@ -35,11 +35,11 @@ class QuaternionMathNode(ArmLogicTreeNode): else: break if (self.property0 == 'Module'): - self.add_output('NodeSocketFloat', 'Module') # Module + self.add_output('ArmFloatSocket', 'Module') # Module if (self.property0 == 'DotProduct'): - self.add_output('NodeSocketFloat', 'Scalar') # DotProduct + self.add_output('ArmFloatSocket', 'Scalar') # DotProduct if (self.property0 == 'ToAxisAngle'): - self.add_output('NodeSocketFloat', 'To Axis Angle') # ToAxisAngle + self.add_output('ArmFloatSocket', 'To Axis Angle') # ToAxisAngle property1: HaxeBoolProperty('property1', name='Separator Out', default=False, set=set_bool, get=get_bool) @@ -88,48 +88,48 @@ class QuaternionMathNode(ArmLogicTreeNode): # Many arguments: Add, Subtract, DotProduct, Multiply, MultiplyFloat if (self.get_count_in(select_current) == 0): if (select_current == "MultiplyFloats"): - self.add_input('NodeSocketVector', 'Quaternion ' + str(len(self.inputs))) - self.add_input('NodeSocketFloat', 'Value ' + str(len(self.inputs))) + self.add_input('ArmVectorSocket', 'Quaternion ' + str(len(self.inputs))) + self.add_input('ArmFloatSocket', 'Value ' + str(len(self.inputs))) else: while (len(self.inputs) < 2): - self.add_input('NodeSocketVector', 'Quaternion ' + str(len(self.inputs))) + self.add_input('ArmVectorSocket', 'Quaternion ' + str(len(self.inputs))) if (select_current == 'DotProduct'): - self.add_output('NodeSocketFloat', 'Scalar') + self.add_output('ArmFloatSocket', 'Scalar') # 3 arguments: Lerp, Slerp, FromAxisAngle, FromEuler if (self.get_count_in(select_current) == 3): if (select_current == 'Lerp') or (select_current == 'Slerp'): while (len(self.inputs) < 3): - self.add_input('NodeSocketVector', 'From') - self.add_input('NodeSocketVector', 'To') - self.add_input('NodeSocketFloat', 'T') + self.add_input('ArmVectorSocket', 'From') + self.add_input('ArmVectorSocket', 'To') + self.add_input('ArmFloatSocket', 'T') if (select_current == 'FromAxisAngle'): - self.add_input('NodeSocketVector', 'Quaternion') - self.add_input('NodeSocketVector', 'Axis') - self.add_input('NodeSocketFloat', 'Angle') + self.add_input('ArmVectorSocket', 'Quaternion') + self.add_input('ArmVectorSocket', 'Axis') + self.add_input('ArmFloatSocket', 'Angle') if (select_current == 'FromEuler'): - self.add_input('NodeSocketFloat', 'X') - self.add_input('NodeSocketFloat', 'Y') - self.add_input('NodeSocketFloat', 'Z') + self.add_input('ArmFloatSocket', 'X') + self.add_input('ArmFloatSocket', 'Y') + self.add_input('ArmFloatSocket', 'Z') # 2 arguments: FromTo, FromMat, FromRotationMat, ToAxisAngle if (self.get_count_in(select_current) == 2): if (select_current == 'FromTo'): - self.add_input('NodeSocketVector', 'Vector ' + str(len(self.inputs))) - self.add_input('NodeSocketVector', 'Vector ' + str(len(self.inputs))) + self.add_input('ArmVectorSocket', 'Vector ' + str(len(self.inputs))) + self.add_input('ArmVectorSocket', 'Vector ' + str(len(self.inputs))) if (select_current == 'FromMat') or (select_current == 'FromRotationMat'): - self.add_input('NodeSocketVector', 'Quaternion') - self.add_input('NodeSocketShader', 'Matrix') + self.add_input('ArmVectorSocket', 'Quaternion') + self.add_input('ArmDynamicSocket', 'Matrix') if (select_current == 'ToAxisAngle'): - self.add_input('NodeSocketVector', 'Quaternion') - self.add_input('NodeSocketVector', 'Axis') - self.add_output('NodeSocketFloat', 'Angle') + self.add_input('ArmVectorSocket', 'Quaternion') + self.add_input('ArmVectorSocket', 'Axis') + self.add_output('ArmFloatSocket', 'Angle') # 1 argument: Module, Normalize, GetEuler if (self.get_count_in(select_current) == 1): - self.add_input('NodeSocketVector', 'Quaternion') + self.add_input('ArmVectorSocket', 'Quaternion') if (select_current == 'Module'): - self.add_output('NodeSocketFloat', 'Module') + self.add_output('ArmFloatSocket', 'Module') self['property0'] = value property0: HaxeEnumProperty( @@ -157,9 +157,9 @@ class QuaternionMathNode(ArmLogicTreeNode): def init(self, context): super(QuaternionMathNode, self).init(context) - self.add_input('NodeSocketVector', 'Quaternion 0', default_value=[0.0, 0.0, 0.0]) - self.add_input('NodeSocketVector', 'Quaternion 1', default_value=[0.0, 0.0, 0.0]) - self.add_output('NodeSocketVector', 'Result') + self.add_input('ArmVectorSocket', 'Quaternion 0', default_value=[0.0, 0.0, 0.0]) + self.add_input('ArmVectorSocket', 'Quaternion 1', default_value=[0.0, 0.0, 0.0]) + self.add_output('ArmVectorSocket', 'Result') def draw_buttons(self, context, layout): layout.prop(self, 'property1') # Separator Out @@ -175,9 +175,9 @@ class QuaternionMathNode(ArmLogicTreeNode): else: op.name_format = 'Value {0}' if (self.property0 == "MultiplyFloats"): - op.socket_type = 'NodeSocketFloat' + op.socket_type = 'ArmFloatSocket' else: - op.socket_type = 'NodeSocketVector' + op.socket_type = 'ArmVectorSocket' column = row.column(align=True) op = column.operator('arm.node_remove_input', text='', icon='X', emboss=True) op.node_index = str(id(self)) diff --git a/blender/arm/logicnode/math/LN_rad_to_deg.py b/blender/arm/logicnode/math/LN_rad_to_deg.py index 2e13666b..2335acb2 100644 --- a/blender/arm/logicnode/math/LN_rad_to_deg.py +++ b/blender/arm/logicnode/math/LN_rad_to_deg.py @@ -9,6 +9,6 @@ class RadToDegNode(ArmLogicTreeNode): def init(self, context): super(RadToDegNode, self).init(context) - self.add_input('NodeSocketFloat', 'Radians') + self.add_input('ArmFloatSocket', 'Radians') - self.add_output('NodeSocketFloat', 'Degrees') + self.add_output('ArmFloatSocket', 'Degrees') diff --git a/blender/arm/logicnode/math/LN_screen_to_world_space.py b/blender/arm/logicnode/math/LN_screen_to_world_space.py index b01ac4e1..0a6c2375 100644 --- a/blender/arm/logicnode/math/LN_screen_to_world_space.py +++ b/blender/arm/logicnode/math/LN_screen_to_world_space.py @@ -14,24 +14,24 @@ class ScreenToWorldSpaceNode(ArmLogicTreeNode): def init(self, context): super(ScreenToWorldSpaceNode, self).init(context) - self.add_input('NodeSocketInt', 'Screen X') - self.add_input('NodeSocketInt', 'Screen Y') + self.add_input('ArmIntSocket', 'Screen X') + self.add_input('ArmIntSocket', 'Screen Y') - self.add_output('NodeSocketVector', 'World') - self.add_output('NodeSocketVector', 'Direction') + self.add_output('ArmVectorSocket', 'World') + self.add_output('ArmVectorSocket', 'Direction') def draw_buttons(self, context, layout): layout.prop(self, 'property0') # Separator Out if self.property0: if len(self.outputs) < self.max_outputs: self.outputs.remove(self.outputs.values()[-1]) # Direction vector - self.add_output('NodeSocketFloat', 'X') # World X - self.add_output('NodeSocketFloat', 'Y') # World Y - self.add_output('NodeSocketFloat', 'Z') # World Z - self.add_output('NodeSocketVector', 'Direction') # Vector - self.add_output('NodeSocketFloat', 'X') # Direction X - self.add_output('NodeSocketFloat', 'Y') # Direction Y - self.add_output('NodeSocketFloat', 'Z') # Direction Z + self.add_output('ArmFloatSocket', 'X') # World X + self.add_output('ArmFloatSocket', 'Y') # World Y + self.add_output('ArmFloatSocket', 'Z') # World Z + self.add_output('ArmVectorSocket', 'Direction') # Vector + self.add_output('ArmFloatSocket', 'X') # Direction X + self.add_output('ArmFloatSocket', 'Y') # Direction Y + self.add_output('ArmFloatSocket', 'Z') # Direction Z else: if len(self.outputs) == self.max_outputs: self.outputs.remove(self.outputs.values()[-1]) # Z @@ -41,4 +41,4 @@ class ScreenToWorldSpaceNode(ArmLogicTreeNode): self.outputs.remove(self.outputs.values()[-1]) # Z self.outputs.remove(self.outputs.values()[-1]) # Y self.outputs.remove(self.outputs.values()[-1]) # X - self.add_output('NodeSocketVector', 'Direction') + self.add_output('ArmVectorSocket', 'Direction') diff --git a/blender/arm/logicnode/math/LN_separate_quaternion.py b/blender/arm/logicnode/math/LN_separate_quaternion.py index fcd9abf6..10d81ded 100644 --- a/blender/arm/logicnode/math/LN_separate_quaternion.py +++ b/blender/arm/logicnode/math/LN_separate_quaternion.py @@ -9,9 +9,9 @@ class SeparateQuaternionNode(ArmLogicTreeNode): def init(self, context): super(SeparateQuaternionNode, self).init(context) - self.add_input('NodeSocketVector', 'Quaternion') + self.add_input('ArmVectorSocket', 'Quaternion') - self.add_output('NodeSocketFloat', 'X') - self.add_output('NodeSocketFloat', 'Y') - self.add_output('NodeSocketFloat', 'Z') - self.add_output('NodeSocketFloat', 'W') + self.add_output('ArmFloatSocket', 'X') + self.add_output('ArmFloatSocket', 'Y') + self.add_output('ArmFloatSocket', 'Z') + self.add_output('ArmFloatSocket', 'W') diff --git a/blender/arm/logicnode/math/LN_separate_rgb.py b/blender/arm/logicnode/math/LN_separate_rgb.py index b95c3e32..e76766f1 100644 --- a/blender/arm/logicnode/math/LN_separate_rgb.py +++ b/blender/arm/logicnode/math/LN_separate_rgb.py @@ -9,8 +9,8 @@ class SeparateColorNode(ArmLogicTreeNode): def init(self, context): super(SeparateColorNode, self).init(context) - self.add_input('NodeSocketColor', 'Color', default_value=[1.0, 1.0, 1.0, 1.0]) + self.add_input('ArmColorSocket', 'Color', default_value=[1.0, 1.0, 1.0, 1.0]) - self.add_output('NodeSocketFloat', 'R') - self.add_output('NodeSocketFloat', 'G') - self.add_output('NodeSocketFloat', 'B') + self.add_output('ArmFloatSocket', 'R') + self.add_output('ArmFloatSocket', 'G') + self.add_output('ArmFloatSocket', 'B') diff --git a/blender/arm/logicnode/math/LN_separate_xyz.py b/blender/arm/logicnode/math/LN_separate_xyz.py index d1cc1316..c2e33ecd 100644 --- a/blender/arm/logicnode/math/LN_separate_xyz.py +++ b/blender/arm/logicnode/math/LN_separate_xyz.py @@ -9,8 +9,8 @@ class SeparateVectorNode(ArmLogicTreeNode): def init(self, context): super(SeparateVectorNode, self).init(context) - self.add_input('NodeSocketVector', 'Vector') + self.add_input('ArmVectorSocket', 'Vector') - self.add_output('NodeSocketFloat', 'X') - self.add_output('NodeSocketFloat', 'Y') - self.add_output('NodeSocketFloat', 'Z') + self.add_output('ArmFloatSocket', 'X') + self.add_output('ArmFloatSocket', 'Y') + self.add_output('ArmFloatSocket', 'Z') diff --git a/blender/arm/logicnode/math/LN_vector_clamp.py b/blender/arm/logicnode/math/LN_vector_clamp.py index 3a47c025..744880ef 100644 --- a/blender/arm/logicnode/math/LN_vector_clamp.py +++ b/blender/arm/logicnode/math/LN_vector_clamp.py @@ -9,8 +9,8 @@ class VectorClampToSizeNode(ArmLogicTreeNode): def init(self, context): super(VectorClampToSizeNode, self).init(context) - self.add_input('NodeSocketVector', 'Vector In', default_value=[0.0, 0.0, 0.0]) - self.add_input('NodeSocketFloat', 'Min') - self.add_input('NodeSocketFloat', 'Max') + self.add_input('ArmVectorSocket', 'Vector In', default_value=[0.0, 0.0, 0.0]) + self.add_input('ArmFloatSocket', 'Min') + self.add_input('ArmFloatSocket', 'Max') - self.add_output('NodeSocketVector', 'Vector Out') + self.add_output('ArmVectorSocket', 'Vector Out') diff --git a/blender/arm/logicnode/math/LN_vector_math.py b/blender/arm/logicnode/math/LN_vector_math.py index 517babbf..7654d151 100644 --- a/blender/arm/logicnode/math/LN_vector_math.py +++ b/blender/arm/logicnode/math/LN_vector_math.py @@ -17,15 +17,15 @@ class VectorMathNode(ArmLogicTreeNode): if value: if (self.property0 == 'Length') or (self.property0 == 'Distance') or (self.property0 == 'Dot Product'): self.outputs.remove(self.outputs.values()[-1]) # Distance/Length/Scalar - self.add_output('NodeSocketFloat', 'X') # Result X - self.add_output('NodeSocketFloat', 'Y') # Result Y - self.add_output('NodeSocketFloat', 'Z') # Result Z + self.add_output('ArmFloatSocket', 'X') # Result X + self.add_output('ArmFloatSocket', 'Y') # Result Y + self.add_output('ArmFloatSocket', 'Z') # Result Z if (self.property0 == 'Length'): - self.add_output('NodeSocketFloat', 'Length') # Length + self.add_output('ArmFloatSocket', 'Length') # Length if (self.property0 == 'Distance'): - self.add_output('NodeSocketFloat', 'Distance') # Distance + self.add_output('ArmFloatSocket', 'Distance') # Distance if (self.property0 == 'Dot Product'): - self.add_output('NodeSocketFloat', 'Scalar') # Scalar + self.add_output('ArmFloatSocket', 'Scalar') # Scalar else: if ((self.property0 == 'Length') or (self.property0 == 'Distance') or (self.property0 == 'Dot Product')) and (len(self.outputs) > 1): self.outputs.remove(self.outputs.values()[-1]) # Distance/Length/Scalar @@ -36,11 +36,11 @@ class VectorMathNode(ArmLogicTreeNode): else: break if (self.property0 == 'Length'): - self.add_output('NodeSocketFloat', 'Length') # Length + self.add_output('ArmFloatSocket', 'Length') # Length if (self.property0 == 'Distance'): - self.add_output('NodeSocketFloat', 'Distance') # Distance + self.add_output('ArmFloatSocket', 'Distance') # Distance if (self.property0 == 'Dot Product'): - self.add_output('NodeSocketFloat', 'Scalar') # Scalar + self.add_output('ArmFloatSocket', 'Scalar') # Scalar property1: HaxeBoolProperty('property1', name='Separator Out', default=False, set=set_bool, get=get_bool) @@ -80,12 +80,12 @@ class VectorMathNode(ArmLogicTreeNode): while (len(self.inputs) > 1): self.inputs.remove(self.inputs.values()[-1]) if (select_current == "MultiplyFloats"): - self.add_input('NodeSocketFloat', 'Value ' + str(len(self.inputs))) + self.add_input('ArmFloatSocket', 'Value ' + str(len(self.inputs))) else: while (len(self.inputs) < 2): - self.add_input('NodeSocketVector', 'Value ' + str(len(self.inputs))) + self.add_input('ArmVectorSocket', 'Value ' + str(len(self.inputs))) if (select_current == 'Dot Product'): - self.add_output('NodeSocketFloat', 'Scalar') + self.add_output('ArmFloatSocket', 'Scalar') # 2 arguments: Distance, Reflect if (self.get_count_in(select_current) == 2): count = 2 @@ -94,15 +94,15 @@ class VectorMathNode(ArmLogicTreeNode): while (len(self.inputs) > count): self.inputs.remove(self.inputs.values()[-1]) while (len(self.inputs) < 2): - self.add_input('NodeSocketVector', 'Value ' + str(len(self.inputs))) + self.add_input('ArmVectorSocket', 'Value ' + str(len(self.inputs))) if (select_current == 'Distance'): - self.add_output('NodeSocketFloat', 'Distance') + self.add_output('ArmFloatSocket', 'Distance') # 1 argument: Normalize, Length if (self.get_count_in(select_current) == 1): while (len(self.inputs) > 1): self.inputs.remove(self.inputs.values()[-1]) if (select_current == 'Length'): - self.add_output('NodeSocketFloat', 'Length') + self.add_output('ArmFloatSocket', 'Length') self['property0'] = value property0: HaxeEnumProperty( @@ -125,10 +125,10 @@ class VectorMathNode(ArmLogicTreeNode): def init(self, context): super(VectorMathNode, self).init(context) - self.add_input('NodeSocketVector', 'Value 0', default_value=[0.0, 0.0, 0.0]) - self.add_input('NodeSocketVector', 'Value 1', default_value=[0.0, 0.0, 0.0]) + self.add_input('ArmVectorSocket', 'Value 0', default_value=[0.0, 0.0, 0.0]) + self.add_input('ArmVectorSocket', 'Value 1', default_value=[0.0, 0.0, 0.0]) - self.add_output('NodeSocketVector', 'Result') + self.add_output('ArmVectorSocket', 'Result') def draw_buttons(self, context, layout): layout.prop(self, 'property1') # Separator Out @@ -141,9 +141,9 @@ class VectorMathNode(ArmLogicTreeNode): op.node_index = str(id(self)) op.name_format = 'Value {0}' if (self.property0 == "MultiplyFloats"): - op.socket_type = 'NodeSocketFloat' + op.socket_type = 'ArmFloatSocket' else: - op.socket_type = 'NodeSocketVector' + op.socket_type = 'ArmVectorSocket' column = row.column(align=True) op = column.operator('arm.node_remove_input', text='', icon='X', emboss=True) op.node_index = str(id(self)) diff --git a/blender/arm/logicnode/math/LN_world_to_screen_space.py b/blender/arm/logicnode/math/LN_world_to_screen_space.py index 53b91b54..6b8f519c 100644 --- a/blender/arm/logicnode/math/LN_world_to_screen_space.py +++ b/blender/arm/logicnode/math/LN_world_to_screen_space.py @@ -9,6 +9,6 @@ class WorldToScreenSpaceNode(ArmLogicTreeNode): def init(self, context): super(WorldToScreenSpaceNode, self).init(context) - self.add_input('NodeSocketVector', 'World') + self.add_input('ArmVectorSocket', 'World') - self.add_output('NodeSocketVector', 'Screen') + self.add_output('ArmVectorSocket', 'Screen') diff --git a/blender/arm/logicnode/miscellaneous/LN_boolean_to_vector.py b/blender/arm/logicnode/miscellaneous/LN_boolean_to_vector.py index acac1ad3..d2999718 100644 --- a/blender/arm/logicnode/miscellaneous/LN_boolean_to_vector.py +++ b/blender/arm/logicnode/miscellaneous/LN_boolean_to_vector.py @@ -8,11 +8,11 @@ class VectorFromBooleanNode(ArmLogicTreeNode): def init(self, context): super(VectorFromBooleanNode, self).init(context) - self.inputs.new('NodeSocketBool', 'X') - self.inputs.new('NodeSocketBool', '-X') - self.inputs.new('NodeSocketBool', 'Y') - self.inputs.new('NodeSocketBool', '-Y') - self.inputs.new('NodeSocketBool', 'Z') - self.inputs.new('NodeSocketBool', '-Z') + self.inputs.new('ArmBoolSocket', 'X') + self.inputs.new('ArmBoolSocket', '-X') + self.inputs.new('ArmBoolSocket', 'Y') + self.inputs.new('ArmBoolSocket', '-Y') + self.inputs.new('ArmBoolSocket', 'Z') + self.inputs.new('ArmBoolSocket', '-Z') - self.outputs.new('NodeSocketVector', 'Vector') + self.outputs.new('ArmVectorSocket', 'Vector') diff --git a/blender/arm/logicnode/miscellaneous/LN_default_if_null.py b/blender/arm/logicnode/miscellaneous/LN_default_if_null.py index 2437c3a2..d2bcf730 100644 --- a/blender/arm/logicnode/miscellaneous/LN_default_if_null.py +++ b/blender/arm/logicnode/miscellaneous/LN_default_if_null.py @@ -12,7 +12,7 @@ class DefaultIfNullNode(ArmLogicTreeNode): def init(self, context): super(DefaultIfNullNode, self).init(context) - self.inputs.new('NodeSocketShader', 'Value In') - self.inputs.new('NodeSocketShader', 'Default') + self.inputs.new('ArmDynamicSocket', 'Value In') + self.inputs.new('ArmDynamicSocket', 'Default') - self.outputs.new('NodeSocketShader', 'Value Out') + self.outputs.new('ArmDynamicSocket', 'Value Out') diff --git a/blender/arm/logicnode/miscellaneous/LN_get_application_time.py b/blender/arm/logicnode/miscellaneous/LN_get_application_time.py index 602d9856..2e4301d6 100644 --- a/blender/arm/logicnode/miscellaneous/LN_get_application_time.py +++ b/blender/arm/logicnode/miscellaneous/LN_get_application_time.py @@ -8,5 +8,5 @@ class TimeNode(ArmLogicTreeNode): def init(self, context): super(TimeNode, self).init(context) - self.add_output('NodeSocketFloat', 'Time') - self.add_output('NodeSocketFloat', 'Delta') + self.add_output('ArmFloatSocket', 'Time') + self.add_output('ArmFloatSocket', 'Delta') diff --git a/blender/arm/logicnode/miscellaneous/LN_get_debug_console_settings.py b/blender/arm/logicnode/miscellaneous/LN_get_debug_console_settings.py index 2ea657b0..5bd6653e 100644 --- a/blender/arm/logicnode/miscellaneous/LN_get_debug_console_settings.py +++ b/blender/arm/logicnode/miscellaneous/LN_get_debug_console_settings.py @@ -8,6 +8,6 @@ class GetDebugConsoleSettings(ArmLogicTreeNode): def init(self, context): super(GetDebugConsoleSettings, self).init(context) - self.add_output('NodeSocketBool', 'Visible') - self.add_output('NodeSocketFloat', 'Scale') - self.add_output('NodeSocketString', 'Position') + self.add_output('ArmBoolSocket', 'Visible') + self.add_output('ArmFloatSocket', 'Scale') + self.add_output('ArmStringSocket', 'Position') diff --git a/blender/arm/logicnode/miscellaneous/LN_get_display_resolution.py b/blender/arm/logicnode/miscellaneous/LN_get_display_resolution.py index 560d053f..42399587 100644 --- a/blender/arm/logicnode/miscellaneous/LN_get_display_resolution.py +++ b/blender/arm/logicnode/miscellaneous/LN_get_display_resolution.py @@ -12,5 +12,5 @@ class DisplayInfoNode(ArmLogicTreeNode): def init(self, context): super(DisplayInfoNode, self).init(context) - self.add_output('NodeSocketInt', 'Width') - self.add_output('NodeSocketInt', 'Height') + self.add_output('ArmIntSocket', 'Width') + self.add_output('ArmIntSocket', 'Height') diff --git a/blender/arm/logicnode/miscellaneous/LN_get_fps.py b/blender/arm/logicnode/miscellaneous/LN_get_fps.py index bd2b76ae..cd693644 100644 --- a/blender/arm/logicnode/miscellaneous/LN_get_fps.py +++ b/blender/arm/logicnode/miscellaneous/LN_get_fps.py @@ -8,4 +8,4 @@ class GetFPSNode(ArmLogicTreeNode): def init(self, context): super(GetFPSNode, self).init(context) - self.add_output('NodeSocketInt', 'Count') + self.add_output('ArmIntSocket', 'Count') diff --git a/blender/arm/logicnode/miscellaneous/LN_get_window_resolution.py b/blender/arm/logicnode/miscellaneous/LN_get_window_resolution.py index 8fd5b504..1870da26 100644 --- a/blender/arm/logicnode/miscellaneous/LN_get_window_resolution.py +++ b/blender/arm/logicnode/miscellaneous/LN_get_window_resolution.py @@ -12,5 +12,5 @@ class WindowInfoNode(ArmLogicTreeNode): def init(self, context): super(WindowInfoNode, self).init(context) - self.add_output('NodeSocketInt', 'Width') - self.add_output('NodeSocketInt', 'Height') + self.add_output('ArmIntSocket', 'Width') + self.add_output('ArmIntSocket', 'Height') diff --git a/blender/arm/logicnode/miscellaneous/LN_set_debug_console_settings.py b/blender/arm/logicnode/miscellaneous/LN_set_debug_console_settings.py index 2a02a150..56d64c89 100644 --- a/blender/arm/logicnode/miscellaneous/LN_set_debug_console_settings.py +++ b/blender/arm/logicnode/miscellaneous/LN_set_debug_console_settings.py @@ -16,8 +16,8 @@ class SetDebugConsoleSettings(ArmLogicTreeNode): def init(self, context): super(SetDebugConsoleSettings, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketBool', 'Visible') - self.add_input('NodeSocketFloat', 'Scale') + self.add_input('ArmBoolSocket', 'Visible') + self.add_input('ArmFloatSocket', 'Scale') self.inputs[-1].default_value = 1.0 self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/miscellaneous/LN_set_time_scale.py b/blender/arm/logicnode/miscellaneous/LN_set_time_scale.py index aff8c194..3cad6073 100644 --- a/blender/arm/logicnode/miscellaneous/LN_set_time_scale.py +++ b/blender/arm/logicnode/miscellaneous/LN_set_time_scale.py @@ -9,6 +9,6 @@ class SetTimeScaleNode(ArmLogicTreeNode): def init(self, context): super(SetTimeScaleNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketFloat', 'Scale', default_value=1.0) + self.add_input('ArmFloatSocket', 'Scale', default_value=1.0) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/miscellaneous/LN_sleep.py b/blender/arm/logicnode/miscellaneous/LN_sleep.py index 0d2e4c82..778bd260 100644 --- a/blender/arm/logicnode/miscellaneous/LN_sleep.py +++ b/blender/arm/logicnode/miscellaneous/LN_sleep.py @@ -10,6 +10,6 @@ class SleepNode(ArmLogicTreeNode): def init(self, context): super(SleepNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketFloat', 'Time') + self.add_input('ArmFloatSocket', 'Time') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/miscellaneous/LN_timer.py b/blender/arm/logicnode/miscellaneous/LN_timer.py index d74d7197..0d9e8ce2 100644 --- a/blender/arm/logicnode/miscellaneous/LN_timer.py +++ b/blender/arm/logicnode/miscellaneous/LN_timer.py @@ -11,16 +11,16 @@ class TimerNode(ArmLogicTreeNode): self.add_input('ArmNodeSocketAction', 'Start') self.add_input('ArmNodeSocketAction', 'Pause') self.add_input('ArmNodeSocketAction', 'Stop') - self.add_input('NodeSocketFloat', 'Duration', default_value=1.0) - self.add_input('NodeSocketInt', 'Repeat') + self.add_input('ArmFloatSocket', 'Duration', default_value=1.0) + self.add_input('ArmIntSocket', 'Repeat') self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmNodeSocketAction', 'Done') - self.add_output('NodeSocketBool', 'Running') - self.add_output('NodeSocketInt', 'Time Passed') - self.add_output('NodeSocketInt', 'Time Left') - self.add_output('NodeSocketFloat', 'Progress') - self.add_output('NodeSocketFloat', 'Repetitions') + self.add_output('ArmBoolSocket', 'Running') + self.add_output('ArmIntSocket', 'Time Passed') + self.add_output('ArmIntSocket', 'Time Left') + self.add_output('ArmFloatSocket', 'Progress') + self.add_output('ArmFloatSocket', 'Repetitions') def draw_label(self) -> str: inp_duration = self.inputs['Duration'] diff --git a/blender/arm/logicnode/native/LN_call_haxe_static.py b/blender/arm/logicnode/native/LN_call_haxe_static.py index eb288074..3b266d22 100644 --- a/blender/arm/logicnode/native/LN_call_haxe_static.py +++ b/blender/arm/logicnode/native/LN_call_haxe_static.py @@ -13,7 +13,7 @@ class CallHaxeStaticNode(ArmLogicTreeNode): def init(self, context): super(CallHaxeStaticNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Function') + self.add_input('ArmStringSocket', 'Function') self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketShader', 'Result') + self.add_output('ArmDynamicSocket', 'Result') diff --git a/blender/arm/logicnode/native/LN_detect_mobile_browser.py b/blender/arm/logicnode/native/LN_detect_mobile_browser.py index d49f62cf..e2e7afed 100644 --- a/blender/arm/logicnode/native/LN_detect_mobile_browser.py +++ b/blender/arm/logicnode/native/LN_detect_mobile_browser.py @@ -8,4 +8,4 @@ class DetectMobileBrowserNode(ArmLogicTreeNode): def init(self, context): super(DetectMobileBrowserNode, self).init(context) - self.add_output('NodeSocketBool', 'Mobile') \ No newline at end of file + self.add_output('ArmBoolSocket', 'Mobile') diff --git a/blender/arm/logicnode/native/LN_expression.py b/blender/arm/logicnode/native/LN_expression.py index d2d7e853..092612ed 100644 --- a/blender/arm/logicnode/native/LN_expression.py +++ b/blender/arm/logicnode/native/LN_expression.py @@ -16,7 +16,7 @@ class ExpressionNode(ArmLogicTreeNode): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketShader', 'Result') + self.add_output('ArmDynamicSocket', 'Result') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/native/LN_get_haxe_property.py b/blender/arm/logicnode/native/LN_get_haxe_property.py index 20721fc7..71d1db4e 100644 --- a/blender/arm/logicnode/native/LN_get_haxe_property.py +++ b/blender/arm/logicnode/native/LN_get_haxe_property.py @@ -11,7 +11,7 @@ class GetHaxePropertyNode(ArmLogicTreeNode): def init(self, context): super(GetHaxePropertyNode, self).init(context) - self.add_input('NodeSocketShader', 'Dynamic') - self.add_input('NodeSocketString', 'Property') + self.add_input('ArmDynamicSocket', 'Dynamic') + self.add_input('ArmStringSocket', 'Property') - self.add_output('NodeSocketShader', 'Value') + self.add_output('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/native/LN_get_system_language.py b/blender/arm/logicnode/native/LN_get_system_language.py index 6989f361..4c0af5e2 100644 --- a/blender/arm/logicnode/native/LN_get_system_language.py +++ b/blender/arm/logicnode/native/LN_get_system_language.py @@ -9,4 +9,4 @@ class GetSystemLanguage(ArmLogicTreeNode): def init(self, context): super(GetSystemLanguage, self).init(context) - self.add_output('NodeSocketString', 'Language') + self.add_output('ArmStringSocket', 'Language') diff --git a/blender/arm/logicnode/native/LN_get_system_name.py b/blender/arm/logicnode/native/LN_get_system_name.py index 2f7bf8dc..95f71053 100644 --- a/blender/arm/logicnode/native/LN_get_system_name.py +++ b/blender/arm/logicnode/native/LN_get_system_name.py @@ -10,9 +10,9 @@ class GetSystemName(ArmLogicTreeNode): def init(self, context): super(GetSystemName, self).init(context) - self.add_output('NodeSocketString', 'System Name') - self.add_output('NodeSocketBool', 'Windows') - self.add_output('NodeSocketBool', 'Linux') - self.add_output('NodeSocketBool', 'Mac') - self.add_output('NodeSocketBool', 'HTML5') - self.add_output('NodeSocketBool', 'Android') + self.add_output('ArmStringSocket', 'System Name') + self.add_output('ArmBoolSocket', 'Windows') + self.add_output('ArmBoolSocket', 'Linux') + self.add_output('ArmBoolSocket', 'Mac') + self.add_output('ArmBoolSocket', 'HTML5') + self.add_output('ArmBoolSocket', 'Android') diff --git a/blender/arm/logicnode/native/LN_loadUrl.py b/blender/arm/logicnode/native/LN_loadUrl.py index c180f456..ee885e80 100644 --- a/blender/arm/logicnode/native/LN_loadUrl.py +++ b/blender/arm/logicnode/native/LN_loadUrl.py @@ -9,4 +9,4 @@ class LoadUrlNode(ArmLogicTreeNode): def init(self, context): super(LoadUrlNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'URL') + self.add_input('ArmStringSocket', 'URL') diff --git a/blender/arm/logicnode/native/LN_print.py b/blender/arm/logicnode/native/LN_print.py index b04ea907..8c3310a2 100644 --- a/blender/arm/logicnode/native/LN_print.py +++ b/blender/arm/logicnode/native/LN_print.py @@ -9,6 +9,6 @@ class PrintNode(ArmLogicTreeNode): def init(self, context): super(PrintNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'String') + self.add_input('ArmStringSocket', 'String') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/native/LN_read_file.py b/blender/arm/logicnode/native/LN_read_file.py index e87d152a..a42c269d 100644 --- a/blender/arm/logicnode/native/LN_read_file.py +++ b/blender/arm/logicnode/native/LN_read_file.py @@ -12,8 +12,8 @@ class ReadFileNode(ArmLogicTreeNode): def init(self, context): super(ReadFileNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'File') - self.add_input('NodeSocketBool', 'Use cache', default_value=1) + self.add_input('ArmStringSocket', 'File') + self.add_input('ArmBoolSocket', 'Use cache', default_value=1) self.add_output('ArmNodeSocketAction', 'Loaded') - self.add_output('NodeSocketString', 'String') + self.add_output('ArmStringSocket', 'String') diff --git a/blender/arm/logicnode/native/LN_read_json.py b/blender/arm/logicnode/native/LN_read_json.py index 24192ce8..7bb6b04e 100644 --- a/blender/arm/logicnode/native/LN_read_json.py +++ b/blender/arm/logicnode/native/LN_read_json.py @@ -12,8 +12,8 @@ class ReadJsonNode(ArmLogicTreeNode): def init(self, context): super(ReadJsonNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'File') - self.add_input('NodeSocketBool', 'Use cache', default_value=1) + self.add_input('ArmStringSocket', 'File') + self.add_input('ArmBoolSocket', 'Use cache', default_value=1) self.add_output('ArmNodeSocketAction', 'Loaded') - self.add_output('NodeSocketShader', 'Dynamic') + self.add_output('ArmDynamicSocket', 'Dynamic') diff --git a/blender/arm/logicnode/native/LN_read_storage.py b/blender/arm/logicnode/native/LN_read_storage.py index af9fd7c9..144c5bef 100644 --- a/blender/arm/logicnode/native/LN_read_storage.py +++ b/blender/arm/logicnode/native/LN_read_storage.py @@ -11,7 +11,7 @@ class ReadStorageNode(ArmLogicTreeNode): def init(self, context): super(ReadStorageNode, self).init(context) - self.add_input('NodeSocketString', 'Key') - self.add_input('NodeSocketString', 'Default') + self.add_input('ArmStringSocket', 'Key') + self.add_input('ArmStringSocket', 'Default') - self.add_output('NodeSocketShader', 'Value') + self.add_output('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/native/LN_script.py b/blender/arm/logicnode/native/LN_script.py index c308bfe6..4d04ddc9 100644 --- a/blender/arm/logicnode/native/LN_script.py +++ b/blender/arm/logicnode/native/LN_script.py @@ -23,7 +23,7 @@ class ScriptNode(ArmLogicTreeNode): self.add_input('ArmNodeSocketArray', 'Array') self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketShader', 'Result') + self.add_output('ArmDynamicSocket', 'Result') def draw_buttons(self, context, layout): layout.prop_search(self, 'property0_', bpy.data, 'texts', icon='NONE', text='') diff --git a/blender/arm/logicnode/native/LN_set_haxe_property.py b/blender/arm/logicnode/native/LN_set_haxe_property.py index 443123ff..664fbc0a 100644 --- a/blender/arm/logicnode/native/LN_set_haxe_property.py +++ b/blender/arm/logicnode/native/LN_set_haxe_property.py @@ -12,8 +12,8 @@ class SetHaxePropertyNode(ArmLogicTreeNode): def init(self, context): super(SetHaxePropertyNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Dynamic') - self.add_input('NodeSocketString', 'Property') - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmDynamicSocket', 'Dynamic') + self.add_input('ArmStringSocket', 'Property') + self.add_input('ArmDynamicSocket', 'Value') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/native/LN_set_vibrate.py b/blender/arm/logicnode/native/LN_set_vibrate.py index 6136a47e..bc340e96 100644 --- a/blender/arm/logicnode/native/LN_set_vibrate.py +++ b/blender/arm/logicnode/native/LN_set_vibrate.py @@ -11,7 +11,7 @@ class SetVibrateNode(ArmLogicTreeNode): def init(self, context): super(SetVibrateNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketInt', 'Milliseconds') + self.add_input('ArmIntSocket', 'Milliseconds') self.inputs[-1].default_value = 100 self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/native/LN_write_file.py b/blender/arm/logicnode/native/LN_write_file.py index 6b04d57d..683cec21 100644 --- a/blender/arm/logicnode/native/LN_write_file.py +++ b/blender/arm/logicnode/native/LN_write_file.py @@ -12,5 +12,5 @@ class WriteFileNode(ArmLogicTreeNode): def init(self, context): super(WriteFileNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'File') - self.add_input('NodeSocketString', 'String') + self.add_input('ArmStringSocket', 'File') + self.add_input('ArmStringSocket', 'String') diff --git a/blender/arm/logicnode/native/LN_write_json.py b/blender/arm/logicnode/native/LN_write_json.py index 366fc008..1694e353 100644 --- a/blender/arm/logicnode/native/LN_write_json.py +++ b/blender/arm/logicnode/native/LN_write_json.py @@ -12,5 +12,5 @@ class WriteJsonNode(ArmLogicTreeNode): def init(self, context): super(WriteJsonNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'File') - self.add_input('NodeSocketShader', 'Dynamic') + self.add_input('ArmStringSocket', 'File') + self.add_input('ArmDynamicSocket', 'Dynamic') diff --git a/blender/arm/logicnode/native/LN_write_storage.py b/blender/arm/logicnode/native/LN_write_storage.py index b829a2bb..bc9a58b0 100644 --- a/blender/arm/logicnode/native/LN_write_storage.py +++ b/blender/arm/logicnode/native/LN_write_storage.py @@ -12,7 +12,7 @@ class WriteStorageNode(ArmLogicTreeNode): def init(self, context): super(WriteStorageNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Key') - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmStringSocket', 'Key') + self.add_input('ArmDynamicSocket', 'Value') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/navmesh/LN_go_to_location.py b/blender/arm/logicnode/navmesh/LN_go_to_location.py index eba3cf97..be97f2eb 100644 --- a/blender/arm/logicnode/navmesh/LN_go_to_location.py +++ b/blender/arm/logicnode/navmesh/LN_go_to_location.py @@ -10,7 +10,7 @@ class GoToLocationNode(ArmLogicTreeNode): super(GoToLocationNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketShader', 'Location') + self.add_input('ArmDynamicSocket', 'Location') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/navmesh/LN_navigable_location.py b/blender/arm/logicnode/navmesh/LN_navigable_location.py index 5d25dc36..f6850bbc 100644 --- a/blender/arm/logicnode/navmesh/LN_navigable_location.py +++ b/blender/arm/logicnode/navmesh/LN_navigable_location.py @@ -8,4 +8,4 @@ class NavigableLocationNode(ArmLogicTreeNode): def init(self, context): super(NavigableLocationNode, self).init(context) - self.add_output('NodeSocketShader', 'Location') + self.add_output('ArmDynamicSocket', 'Location') diff --git a/blender/arm/logicnode/navmesh/LN_pick_navmesh_location.py b/blender/arm/logicnode/navmesh/LN_pick_navmesh_location.py index c70dfba8..063174c7 100644 --- a/blender/arm/logicnode/navmesh/LN_pick_navmesh_location.py +++ b/blender/arm/logicnode/navmesh/LN_pick_navmesh_location.py @@ -9,6 +9,6 @@ class PickLocationNode(ArmLogicTreeNode): def init(self, context): super(PickLocationNode, self).init(context) self.add_input('ArmNodeSocketObject', 'NavMesh') - self.add_input('NodeSocketVector', 'Screen Coords') + self.add_input('ArmVectorSocket', 'Screen Coords') - self.add_output('NodeSocketVector', 'Location') + self.add_output('ArmVectorSocket', 'Location') diff --git a/blender/arm/logicnode/object/LN_get_distance.py b/blender/arm/logicnode/object/LN_get_distance.py index 0d4b5a3a..13b6a5be 100644 --- a/blender/arm/logicnode/object/LN_get_distance.py +++ b/blender/arm/logicnode/object/LN_get_distance.py @@ -14,4 +14,4 @@ class GetDistanceNode(ArmLogicTreeNode): self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmNodeSocketObject', 'Object') - self.add_output('NodeSocketFloat', 'Distance') + self.add_output('ArmFloatSocket', 'Distance') diff --git a/blender/arm/logicnode/object/LN_get_object_by_name.py b/blender/arm/logicnode/object/LN_get_object_by_name.py index ae39b4e1..92c7220d 100644 --- a/blender/arm/logicnode/object/LN_get_object_by_name.py +++ b/blender/arm/logicnode/object/LN_get_object_by_name.py @@ -12,6 +12,6 @@ class GetObjectNode(ArmLogicTreeNode): def init(self, context): super(GetObjectNode, self).init(context) - self.add_input('NodeSocketString', 'Name') + self.add_input('ArmStringSocket', 'Name') self.add_output('ArmNodeSocketObject', 'Object') diff --git a/blender/arm/logicnode/object/LN_get_object_child.py b/blender/arm/logicnode/object/LN_get_object_child.py index be1b4de3..8ff69a55 100644 --- a/blender/arm/logicnode/object/LN_get_object_child.py +++ b/blender/arm/logicnode/object/LN_get_object_child.py @@ -19,7 +19,7 @@ class GetChildNode(ArmLogicTreeNode): def init(self, context): super(GetChildNode, self).init(context) self.add_input('ArmNodeSocketObject', 'Parent') - self.add_input('NodeSocketString', 'Child Name') + self.add_input('ArmStringSocket', 'Child Name') self.add_output('ArmNodeSocketObject', 'Child') diff --git a/blender/arm/logicnode/object/LN_get_object_mesh.py b/blender/arm/logicnode/object/LN_get_object_mesh.py index ff9cf047..83d45e35 100644 --- a/blender/arm/logicnode/object/LN_get_object_mesh.py +++ b/blender/arm/logicnode/object/LN_get_object_mesh.py @@ -11,4 +11,4 @@ class GetMeshNode(ArmLogicTreeNode): super(GetMeshNode, self).init(context) self.add_input('ArmNodeSocketObject', 'Object') - self.add_output('NodeSocketShader', 'Mesh') + self.add_output('ArmDynamicSocket', 'Mesh') diff --git a/blender/arm/logicnode/object/LN_get_object_name.py b/blender/arm/logicnode/object/LN_get_object_name.py index 8cd79d98..e6938510 100644 --- a/blender/arm/logicnode/object/LN_get_object_name.py +++ b/blender/arm/logicnode/object/LN_get_object_name.py @@ -11,4 +11,4 @@ class GetNameNode(ArmLogicTreeNode): super(GetNameNode, self).init(context) self.add_input('ArmNodeSocketObject', 'Object') - self.add_output('NodeSocketString', 'Name') + self.add_output('ArmStringSocket', 'Name') diff --git a/blender/arm/logicnode/object/LN_get_object_offscreen.py b/blender/arm/logicnode/object/LN_get_object_offscreen.py index 602ce2d0..1eb38bd8 100644 --- a/blender/arm/logicnode/object/LN_get_object_offscreen.py +++ b/blender/arm/logicnode/object/LN_get_object_offscreen.py @@ -11,6 +11,6 @@ class GetObjectOffscreenNode(ArmLogicTreeNode): super(GetObjectOffscreenNode, self).init(context) self.inputs.new('ArmNodeSocketObject', 'Object') - self.outputs.new('NodeSocketBool', 'Is Object Offscreen') - self.outputs.new('NodeSocketBool', 'Is Mesh Offscreen') - self.outputs.new('NodeSocketBool', 'Is Shadow Offscreen') + self.outputs.new('ArmBoolSocket', 'Is Object Offscreen') + self.outputs.new('ArmBoolSocket', 'Is Mesh Offscreen') + self.outputs.new('ArmBoolSocket', 'Is Shadow Offscreen') diff --git a/blender/arm/logicnode/object/LN_get_object_property.py b/blender/arm/logicnode/object/LN_get_object_property.py index 6c2792cf..ecd62857 100644 --- a/blender/arm/logicnode/object/LN_get_object_property.py +++ b/blender/arm/logicnode/object/LN_get_object_property.py @@ -12,7 +12,7 @@ class GetPropertyNode(ArmLogicTreeNode): def init(self, context): super(GetPropertyNode, self).init(context) self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketString', 'Property') + self.add_input('ArmStringSocket', 'Property') - self.add_output('NodeSocketShader', 'Value') - self.add_output('NodeSocketString', 'Property') + self.add_output('ArmDynamicSocket', 'Value') + self.add_output('ArmStringSocket', 'Property') diff --git a/blender/arm/logicnode/object/LN_get_object_visible.py b/blender/arm/logicnode/object/LN_get_object_visible.py index b04eb1b7..0f3e1b1c 100644 --- a/blender/arm/logicnode/object/LN_get_object_visible.py +++ b/blender/arm/logicnode/object/LN_get_object_visible.py @@ -14,6 +14,6 @@ class GetVisibleNode(ArmLogicTreeNode): super(GetVisibleNode, self).init(context) self.add_input('ArmNodeSocketObject', 'Object') - self.add_output('NodeSocketBool', 'Is Object Visible') - self.add_output('NodeSocketBool', 'Is Mesh Visible') - self.add_output('NodeSocketBool', 'Is Shadow Visible') + self.add_output('ArmBoolSocket', 'Is Object Visible') + self.add_output('ArmBoolSocket', 'Is Mesh Visible') + self.add_output('ArmBoolSocket', 'Is Shadow Visible') diff --git a/blender/arm/logicnode/object/LN_mesh.py b/blender/arm/logicnode/object/LN_mesh.py index d68f2233..cefcca05 100644 --- a/blender/arm/logicnode/object/LN_mesh.py +++ b/blender/arm/logicnode/object/LN_mesh.py @@ -13,7 +13,7 @@ class MeshNode(ArmLogicTreeNode): def init(self, context): super(MeshNode, self).init(context) - self.add_output('NodeSocketShader', 'Mesh', is_var=True) + self.add_output('ArmDynamicSocket', 'Mesh', is_var=True) def draw_buttons(self, context, layout): layout.prop_search(self, 'property0_get', bpy.data, 'meshes', icon='NONE', text='') diff --git a/blender/arm/logicnode/object/LN_remove_object_parent.py b/blender/arm/logicnode/object/LN_remove_object_parent.py index ffe60c13..cbfdb84a 100644 --- a/blender/arm/logicnode/object/LN_remove_object_parent.py +++ b/blender/arm/logicnode/object/LN_remove_object_parent.py @@ -11,6 +11,6 @@ class ClearParentNode(ArmLogicTreeNode): super(ClearParentNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketBool', 'Keep Transform', default_value=True) + self.add_input('ArmBoolSocket', 'Keep Transform', default_value=True) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/object/LN_set_object_mesh.py b/blender/arm/logicnode/object/LN_set_object_mesh.py index 803b25e1..601f36ae 100644 --- a/blender/arm/logicnode/object/LN_set_object_mesh.py +++ b/blender/arm/logicnode/object/LN_set_object_mesh.py @@ -11,6 +11,6 @@ class SetMeshNode(ArmLogicTreeNode): super(SetMeshNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketShader', 'Mesh') + self.add_input('ArmDynamicSocket', 'Mesh') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/object/LN_set_object_name.py b/blender/arm/logicnode/object/LN_set_object_name.py index 921675b9..05f20794 100644 --- a/blender/arm/logicnode/object/LN_set_object_name.py +++ b/blender/arm/logicnode/object/LN_set_object_name.py @@ -11,6 +11,6 @@ class SetNameNode(ArmLogicTreeNode): super(SetNameNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketString', 'Name') + self.add_input('ArmStringSocket', 'Name') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/object/LN_set_object_property.py b/blender/arm/logicnode/object/LN_set_object_property.py index f5f17e3f..b41494fa 100644 --- a/blender/arm/logicnode/object/LN_set_object_property.py +++ b/blender/arm/logicnode/object/LN_set_object_property.py @@ -18,7 +18,7 @@ class SetPropertyNode(ArmLogicTreeNode): super(SetPropertyNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketString', 'Property') - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmStringSocket', 'Property') + self.add_input('ArmDynamicSocket', 'Value') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/object/LN_set_object_visible.py b/blender/arm/logicnode/object/LN_set_object_visible.py index 76fe036f..53e354a6 100644 --- a/blender/arm/logicnode/object/LN_set_object_visible.py +++ b/blender/arm/logicnode/object/LN_set_object_visible.py @@ -21,8 +21,8 @@ class SetVisibleNode(ArmLogicTreeNode): super(SetVisibleNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketBool', 'Visible') - self.add_input('NodeSocketBool', 'Children', default_value=True) + self.add_input('ArmBoolSocket', 'Visible') + self.add_input('ArmBoolSocket', 'Children', default_value=True) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/object/LN_spawn_object.py b/blender/arm/logicnode/object/LN_spawn_object.py index e4e3722a..49097d86 100644 --- a/blender/arm/logicnode/object/LN_spawn_object.py +++ b/blender/arm/logicnode/object/LN_spawn_object.py @@ -11,8 +11,8 @@ class SpawnObjectNode(ArmLogicTreeNode): super(SpawnObjectNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketShader', 'Transform') - self.add_input('NodeSocketBool', 'Children', default_value=True) + self.add_input('ArmDynamicSocket', 'Transform') + self.add_input('ArmBoolSocket', 'Children', default_value=True) self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmNodeSocketObject', 'Object') diff --git a/blender/arm/logicnode/object/LN_spawn_object_by_name.py b/blender/arm/logicnode/object/LN_spawn_object_by_name.py index 970f0bd7..bd629b21 100644 --- a/blender/arm/logicnode/object/LN_spawn_object_by_name.py +++ b/blender/arm/logicnode/object/LN_spawn_object_by_name.py @@ -14,9 +14,9 @@ class SpawnObjectByNameNode(ArmLogicTreeNode): def init(self, context): super(SpawnObjectByNameNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Name') - self.add_input('NodeSocketShader', 'Transform') - self.add_input('NodeSocketBool', 'Children', default_value=True) + self.add_input('ArmStringSocket', 'Name') + self.add_input('ArmDynamicSocket', 'Transform') + self.add_input('ArmBoolSocket', 'Children', default_value=True) self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmNodeSocketObject', 'Object') diff --git a/blender/arm/logicnode/physics/LN_Add_rigid_body.py b/blender/arm/logicnode/physics/LN_Add_rigid_body.py index 13fbea61..7376c096 100644 --- a/blender/arm/logicnode/physics/LN_Add_rigid_body.py +++ b/blender/arm/logicnode/physics/LN_Add_rigid_body.py @@ -83,13 +83,13 @@ class AddRigidBodyNode(ArmLogicTreeNode): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketFloat', 'Mass', 1.0) - self.add_input('NodeSocketBool', 'Active', True) - self.add_input('NodeSocketBool', 'Animated', False) - self.add_input('NodeSocketBool', 'Trigger', False) - self.add_input('NodeSocketFloat', 'Friction', 0.5) - self.add_input('NodeSocketFloat', 'Bounciness', 0.0) - self.add_input('NodeSocketBool', 'Continuous Collision Detection', False) + self.add_input('ArmFloatSocket', 'Mass', 1.0) + self.add_input('ArmBoolSocket', 'Active', True) + self.add_input('ArmBoolSocket', 'Animated', False) + self.add_input('ArmBoolSocket', 'Trigger', False) + self.add_input('ArmFloatSocket', 'Friction', 0.5) + self.add_input('ArmFloatSocket', 'Bounciness', 0.0) + self.add_input('ArmBoolSocket', 'Continuous Collision Detection', False) self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmNodeSocketObject', 'Rigid body') @@ -108,15 +108,15 @@ class AddRigidBodyNode(ArmLogicTreeNode): # Add dynamic input sockets if self.property1_: - self.add_input('NodeSocketBool', 'Collision Margin', False) - self.add_input('NodeSocketFloat', 'Margin', 0.04) - self.add_input('NodeSocketFloat', 'Linear Damping', 0.04) - self.add_input('NodeSocketFloat', 'Angular Damping', 0.1) - self.add_input('NodeSocketBool', 'Use Deacivation') - self.add_input('NodeSocketFloat', 'Linear Velocity Threshold', 0.4) - self.add_input('NodeSocketFloat', 'Angular Velocity Threshold', 0.5) - self.add_input('NodeSocketInt', 'Collision Group', 1) - self.add_input('NodeSocketInt', 'Collision Mask', 1) + self.add_input('ArmBoolSocket', 'Collision Margin', False) + self.add_input('ArmFloatSocket', 'Margin', 0.04) + self.add_input('ArmFloatSocket', 'Linear Damping', 0.04) + self.add_input('ArmFloatSocket', 'Angular Damping', 0.1) + self.add_input('ArmBoolSocket', 'Use Deacivation') + self.add_input('ArmFloatSocket', 'Linear Velocity Threshold', 0.4) + self.add_input('ArmFloatSocket', 'Angular Velocity Threshold', 0.5) + self.add_input('ArmIntSocket', 'Collision Group', 1) + self.add_input('ArmIntSocket', 'Collision Mask', 1) def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/physics/LN_add_physics_constraint.py b/blender/arm/logicnode/physics/LN_add_physics_constraint.py index f8537fa9..89c2d1e0 100644 --- a/blender/arm/logicnode/physics/LN_add_physics_constraint.py +++ b/blender/arm/logicnode/physics/LN_add_physics_constraint.py @@ -91,31 +91,31 @@ class AddPhysicsConstraintNode(ArmLogicTreeNode): while (len(self.inputs) > 7): self.inputs.remove(self.inputs.values()[-1]) #Z ang limits - self.add_input('NodeSocketBool', 'Z angle') - self.add_input('NodeSocketFloat', 'Z ang lower', -45.0) - self.add_input('NodeSocketFloat', 'Z ang upper', 45.0) + self.add_input('ArmBoolSocket', 'Z angle') + self.add_input('ArmFloatSocket', 'Z ang lower', -45.0) + self.add_input('ArmFloatSocket', 'Z ang upper', 45.0) #Arguements for type Slider if (self.get_count_in(select_current) == 3): while (len(self.inputs) > 7): self.inputs.remove(self.inputs.values()[-1]) #X lin limits - self.add_input('NodeSocketBool', 'X linear') - self.add_input('NodeSocketFloat', 'X lin lower') - self.add_input('NodeSocketFloat', 'X lin upper') + self.add_input('ArmBoolSocket', 'X linear') + self.add_input('ArmFloatSocket', 'X lin lower') + self.add_input('ArmFloatSocket', 'X lin upper') #Arguements for type Piston if (self.get_count_in(select_current) == 4): while (len(self.inputs) > 7): self.inputs.remove(self.inputs.values()[-1]) #X lin limits - self.add_input('NodeSocketBool', 'X linear') - self.add_input('NodeSocketFloat', 'X lin lower') - self.add_input('NodeSocketFloat', 'X lin upper') + self.add_input('ArmBoolSocket', 'X linear') + self.add_input('ArmFloatSocket', 'X lin lower') + self.add_input('ArmFloatSocket', 'X lin upper') #X ang limits - self.add_input('NodeSocketBool', 'X angle') - self.add_input('NodeSocketFloat', 'X ang lower', -45.0) - self.add_input('NodeSocketFloat', 'X ang upper', 45.0) + self.add_input('ArmBoolSocket', 'X angle') + self.add_input('ArmFloatSocket', 'X ang lower', -45.0) + self.add_input('ArmFloatSocket', 'X ang upper', 45.0) #Arguements for type GenericSpring if (self.get_count_in(select_current) == 5): @@ -143,9 +143,9 @@ class AddPhysicsConstraintNode(ArmLogicTreeNode): self.add_input('ArmNodeSocketObject', 'Pivot Object') self.add_input('ArmNodeSocketObject', 'RB 1') self.add_input('ArmNodeSocketObject', 'RB 2') - self.add_input('NodeSocketBool', 'Disable Collissions') - self.add_input('NodeSocketBool', 'Breakable') - self.add_input('NodeSocketFloat', 'Breaking Threshold') + self.add_input('ArmBoolSocket', 'Disable Collissions') + self.add_input('ArmBoolSocket', 'Breakable') + self.add_input('ArmFloatSocket', 'Breaking Threshold') self.add_output('ArmNodeSocketAction', 'Out') def draw_buttons(self, context, layout): @@ -163,7 +163,7 @@ class AddPhysicsConstraintNode(ArmLogicTreeNode): column = row.column(align=True) op = column.operator('arm.node_add_input', text='Add Constraint', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketShader' + op.socket_type = 'ArmDynamicSocket' op.name_format = 'Constraint {0}'.format(len(self.inputs) - 6) column1 = row.column(align=True) op = column1.operator('arm.node_remove_input', text='', icon='X', emboss=True) diff --git a/blender/arm/logicnode/physics/LN_apply_force.py b/blender/arm/logicnode/physics/LN_apply_force.py index 57c4a9fa..bfe29b07 100644 --- a/blender/arm/logicnode/physics/LN_apply_force.py +++ b/blender/arm/logicnode/physics/LN_apply_force.py @@ -20,7 +20,7 @@ class ApplyForceNode(ArmLogicTreeNode): super(ApplyForceNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'RB') - self.add_input('NodeSocketVector', 'Force') - self.add_input('NodeSocketBool', 'On Local Axis') + self.add_input('ArmVectorSocket', 'Force') + self.add_input('ArmBoolSocket', 'On Local Axis') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/physics/LN_apply_force_at_location.py b/blender/arm/logicnode/physics/LN_apply_force_at_location.py index 8d83efba..7a9de48e 100644 --- a/blender/arm/logicnode/physics/LN_apply_force_at_location.py +++ b/blender/arm/logicnode/physics/LN_apply_force_at_location.py @@ -23,9 +23,9 @@ class ApplyForceAtLocationNode(ArmLogicTreeNode): super(ApplyForceAtLocationNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'RB') - self.add_input('NodeSocketVector', 'Force') - self.add_input('NodeSocketBool', 'Force On Local Axis') - self.add_input('NodeSocketVector', 'Location') - self.add_input('NodeSocketBool', 'Location On Local Axis') + self.add_input('ArmVectorSocket', 'Force') + self.add_input('ArmBoolSocket', 'Force On Local Axis') + self.add_input('ArmVectorSocket', 'Location') + self.add_input('ArmBoolSocket', 'Location On Local Axis') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/physics/LN_apply_impulse.py b/blender/arm/logicnode/physics/LN_apply_impulse.py index c3ecec3b..ad41b870 100644 --- a/blender/arm/logicnode/physics/LN_apply_impulse.py +++ b/blender/arm/logicnode/physics/LN_apply_impulse.py @@ -20,7 +20,7 @@ class ApplyImpulseNode(ArmLogicTreeNode): super(ApplyImpulseNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'RB') - self.add_input('NodeSocketVector', 'Impulse') - self.add_input('NodeSocketBool', 'On Local Axis') + self.add_input('ArmVectorSocket', 'Impulse') + self.add_input('ArmBoolSocket', 'On Local Axis') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/physics/LN_apply_impulse_at_location.py b/blender/arm/logicnode/physics/LN_apply_impulse_at_location.py index fbd0c53c..54a9c0ef 100644 --- a/blender/arm/logicnode/physics/LN_apply_impulse_at_location.py +++ b/blender/arm/logicnode/physics/LN_apply_impulse_at_location.py @@ -23,9 +23,9 @@ class ApplyImpulseAtLocationNode(ArmLogicTreeNode): super(ApplyImpulseAtLocationNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'RB') - self.add_input('NodeSocketVector', 'Impulse') - self.add_input('NodeSocketBool', 'Impulse On Local Axis') - self.add_input('NodeSocketVector', 'Location') - self.add_input('NodeSocketBool', 'Location On Local Axis') + self.add_input('ArmVectorSocket', 'Impulse') + self.add_input('ArmBoolSocket', 'Impulse On Local Axis') + self.add_input('ArmVectorSocket', 'Location') + self.add_input('ArmBoolSocket', 'Location On Local Axis') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/physics/LN_apply_torque.py b/blender/arm/logicnode/physics/LN_apply_torque.py index 88ca157e..7e00d966 100644 --- a/blender/arm/logicnode/physics/LN_apply_torque.py +++ b/blender/arm/logicnode/physics/LN_apply_torque.py @@ -11,7 +11,7 @@ class ApplyTorqueNode(ArmLogicTreeNode): super(ApplyTorqueNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'RB') - self.add_input('NodeSocketVector', 'Torque') - self.add_input('NodeSocketBool', 'On Local Axis') + self.add_input('ArmVectorSocket', 'Torque') + self.add_input('ArmBoolSocket', 'On Local Axis') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/physics/LN_apply_torque_impulse.py b/blender/arm/logicnode/physics/LN_apply_torque_impulse.py index f067cf7f..91662926 100644 --- a/blender/arm/logicnode/physics/LN_apply_torque_impulse.py +++ b/blender/arm/logicnode/physics/LN_apply_torque_impulse.py @@ -11,7 +11,7 @@ class ApplyTorqueImpulseNode(ArmLogicTreeNode): super(ApplyTorqueImpulseNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'RB') - self.add_input('NodeSocketVector', 'Torque') - self.add_input('NodeSocketBool', 'On Local Axis') + self.add_input('ArmVectorSocket', 'Torque') + self.add_input('ArmBoolSocket', 'On Local Axis') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/physics/LN_get_rb_data.py b/blender/arm/logicnode/physics/LN_get_rb_data.py index 90637ff5..3671516f 100644 --- a/blender/arm/logicnode/physics/LN_get_rb_data.py +++ b/blender/arm/logicnode/physics/LN_get_rb_data.py @@ -10,17 +10,17 @@ class GetRigidBodyDataNode(ArmLogicTreeNode): def init(self, context): self.inputs.new('ArmNodeSocketObject', 'Object') - self.outputs.new('NodeSocketBool', 'Is RB') - self.outputs.new('NodeSocketInt', 'Collision Group') - self.outputs.new('NodeSocketInt', 'Collision Mask') - self.outputs.new('NodeSocketBool', 'Is Animated') - self.outputs.new('NodeSocketBool', 'Is Static') - self.outputs.new('NodeSocketFloat', 'Angular Damping') - self.outputs.new('NodeSocketFloat', 'Linear Damping') - self.outputs.new('NodeSocketFloat', 'Friction') - self.outputs.new('NodeSocketFloat', 'Mass') - #self.outputs.new('NodeSocketString', 'Collision Shape') - #self.outputs.new('NodeSocketInt', 'Activation State') - #self.outputs.new('NodeSocketBool', 'Is Gravity Enabled') - #self.outputs.new(NodeSocketVector', Angular Factor') - #self.outputs.new('NodeSocketVector', Linear Factor') + self.outputs.new('ArmBoolSocket', 'Is RB') + self.outputs.new('ArmIntSocket', 'Collision Group') + self.outputs.new('ArmIntSocket', 'Collision Mask') + self.outputs.new('ArmBoolSocket', 'Is Animated') + self.outputs.new('ArmBoolSocket', 'Is Static') + self.outputs.new('ArmFloatSocket', 'Angular Damping') + self.outputs.new('ArmFloatSocket', 'Linear Damping') + self.outputs.new('ArmFloatSocket', 'Friction') + self.outputs.new('ArmFloatSocket', 'Mass') + #self.outputs.new('ArmStringSocket', 'Collision Shape') + #self.outputs.new('ArmIntSocket', 'Activation State') + #self.outputs.new('ArmBoolSocket', 'Is Gravity Enabled') + #self.outputs.new(ArmVectorSocket', Angular Factor') + #self.outputs.new('ArmVectorSocket', Linear Factor') diff --git a/blender/arm/logicnode/physics/LN_get_rb_velocity.py b/blender/arm/logicnode/physics/LN_get_rb_velocity.py index e4b889f9..33269ccb 100644 --- a/blender/arm/logicnode/physics/LN_get_rb_velocity.py +++ b/blender/arm/logicnode/physics/LN_get_rb_velocity.py @@ -9,8 +9,8 @@ class GetVelocityNode(ArmLogicTreeNode): def init(self, context): super(GetVelocityNode, self).init(context) self.add_input('ArmNodeSocketObject', 'RB') - self.add_input('NodeSocketBool', 'Linear On Local Axis') - self.add_input('NodeSocketBool', 'Angular On Local Axis') + self.add_input('ArmBoolSocket', 'Linear On Local Axis') + self.add_input('ArmBoolSocket', 'Angular On Local Axis') - self.add_output('NodeSocketVector', 'Linear') - self.add_output('NodeSocketVector', 'Angular') + self.add_output('ArmVectorSocket', 'Linear') + self.add_output('ArmVectorSocket', 'Angular') diff --git a/blender/arm/logicnode/physics/LN_get_world_gravity.py b/blender/arm/logicnode/physics/LN_get_world_gravity.py index e91555f5..fe810eec 100644 --- a/blender/arm/logicnode/physics/LN_get_world_gravity.py +++ b/blender/arm/logicnode/physics/LN_get_world_gravity.py @@ -11,4 +11,4 @@ class GetGravityNode(ArmLogicTreeNode): def init(self, context): super(GetGravityNode, self).init(context) - self.add_output('NodeSocketVector', 'World Gravity') + self.add_output('ArmVectorSocket', 'World Gravity') diff --git a/blender/arm/logicnode/physics/LN_has_contact.py b/blender/arm/logicnode/physics/LN_has_contact.py index 9a1eb8da..4bd2151f 100644 --- a/blender/arm/logicnode/physics/LN_has_contact.py +++ b/blender/arm/logicnode/physics/LN_has_contact.py @@ -12,4 +12,4 @@ class HasContactNode(ArmLogicTreeNode): self.add_input('ArmNodeSocketObject', 'RB 1') self.add_input('ArmNodeSocketObject', 'RB 2') - self.add_output('NodeSocketBool', 'Has Contact') + self.add_output('ArmBoolSocket', 'Has Contact') diff --git a/blender/arm/logicnode/physics/LN_has_contact_array.py b/blender/arm/logicnode/physics/LN_has_contact_array.py index 7fafd3e9..e64d342e 100644 --- a/blender/arm/logicnode/physics/LN_has_contact_array.py +++ b/blender/arm/logicnode/physics/LN_has_contact_array.py @@ -12,4 +12,4 @@ class HasContactArrayNode(ArmLogicTreeNode): self.add_input('ArmNodeSocketObject', 'RB') self.add_input('ArmNodeSocketArray', 'RBs') - self.add_output('NodeSocketBool', 'Has Contact') + self.add_output('ArmBoolSocket', 'Has Contact') diff --git a/blender/arm/logicnode/physics/LN_physics_constraint.py b/blender/arm/logicnode/physics/LN_physics_constraint.py index 28dd0f6f..d4d1c539 100644 --- a/blender/arm/logicnode/physics/LN_physics_constraint.py +++ b/blender/arm/logicnode/physics/LN_physics_constraint.py @@ -53,9 +53,9 @@ class PhysicsConstraintNode(ArmLogicTreeNode): def init(self, context): super(PhysicsConstraintNode, self).init(context) - self.add_input('NodeSocketFloat', 'Lower limit') - self.add_input('NodeSocketFloat', 'Upper limit') - self.add_output('NodeSocketShader', 'Constraint') + self.add_input('ArmFloatSocket', 'Lower limit') + self.add_input('ArmFloatSocket', 'Upper limit') + self.add_output('ArmDynamicSocket', 'Constraint') def update_sockets(self, context): while len(self.inputs) > 0: @@ -63,11 +63,11 @@ class PhysicsConstraintNode(ArmLogicTreeNode): # Add dynamic input sockets if self.property2: - self.add_input('NodeSocketFloat', 'Stiffness', 10.0) - self.add_input('NodeSocketFloat', 'Damping', 0.5) + self.add_input('ArmFloatSocket', 'Stiffness', 10.0) + self.add_input('ArmFloatSocket', 'Damping', 0.5) else: - self.add_input('NodeSocketFloat', 'Lower limit') - self.add_input('NodeSocketFloat', 'Upper limit') + self.add_input('ArmFloatSocket', 'Lower limit') + self.add_input('ArmFloatSocket', 'Upper limit') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/physics/LN_pick_rb.py b/blender/arm/logicnode/physics/LN_pick_rb.py index 8497e1f9..65e34f54 100644 --- a/blender/arm/logicnode/physics/LN_pick_rb.py +++ b/blender/arm/logicnode/physics/LN_pick_rb.py @@ -21,8 +21,8 @@ class PickObjectNode(ArmLogicTreeNode): def init(self, context): super(PickObjectNode, self).init(context) - self.add_input('NodeSocketVector', 'Screen Coords') - self.add_input('NodeSocketInt', 'Mask', default_value=1) + self.add_input('ArmVectorSocket', 'Screen Coords') + self.add_input('ArmIntSocket', 'Mask', default_value=1) self.add_output('ArmNodeSocketObject', 'RB') - self.add_output('NodeSocketVector', 'Hit') + self.add_output('ArmVectorSocket', 'Hit') diff --git a/blender/arm/logicnode/physics/LN_ray_cast.py b/blender/arm/logicnode/physics/LN_ray_cast.py index 6792bdb5..79995333 100644 --- a/blender/arm/logicnode/physics/LN_ray_cast.py +++ b/blender/arm/logicnode/physics/LN_ray_cast.py @@ -24,10 +24,10 @@ class RayCastNode(ArmLogicTreeNode): def init(self, context): super(RayCastNode, self).init(context) - self.add_input('NodeSocketVector', 'From') - self.add_input('NodeSocketVector', 'To') - self.add_input('NodeSocketInt', 'Mask', default_value=1) + self.add_input('ArmVectorSocket', 'From') + self.add_input('ArmVectorSocket', 'To') + self.add_input('ArmIntSocket', 'Mask', default_value=1) self.add_output('ArmNodeSocketObject', 'RB') - self.add_output('NodeSocketVector', 'Hit') - self.add_output('NodeSocketVector', 'Normal') + self.add_output('ArmVectorSocket', 'Hit') + self.add_output('ArmVectorSocket', 'Normal') diff --git a/blender/arm/logicnode/physics/LN_set_rb_friction.py b/blender/arm/logicnode/physics/LN_set_rb_friction.py index d2c4e929..c2267732 100644 --- a/blender/arm/logicnode/physics/LN_set_rb_friction.py +++ b/blender/arm/logicnode/physics/LN_set_rb_friction.py @@ -11,6 +11,6 @@ class SetFrictionNode (ArmLogicTreeNode): super(SetFrictionNode, self).init(context) self.inputs.new('ArmNodeSocketAction', 'In') self.inputs.new('ArmNodeSocketObject', 'RB') - self.inputs.new('NodeSocketFloat', 'Friction') + self.inputs.new('ArmFloatSocket', 'Friction') self.outputs.new('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/physics/LN_set_rb_gravity_enabled.py b/blender/arm/logicnode/physics/LN_set_rb_gravity_enabled.py index a7109160..73f0405d 100644 --- a/blender/arm/logicnode/physics/LN_set_rb_gravity_enabled.py +++ b/blender/arm/logicnode/physics/LN_set_rb_gravity_enabled.py @@ -10,6 +10,6 @@ class SetGravityEnabledNode(ArmLogicTreeNode): super(SetGravityEnabledNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'RB') - self.add_input('NodeSocketBool', 'Enabled') + self.add_input('ArmBoolSocket', 'Enabled') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/physics/LN_set_rb_velocity.py b/blender/arm/logicnode/physics/LN_set_rb_velocity.py index 024e980c..48e95bbc 100644 --- a/blender/arm/logicnode/physics/LN_set_rb_velocity.py +++ b/blender/arm/logicnode/physics/LN_set_rb_velocity.py @@ -10,9 +10,9 @@ class SetVelocityNode(ArmLogicTreeNode): super(SetVelocityNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'RB') - self.add_input('NodeSocketVector', 'Linear') - self.add_input('NodeSocketVector', 'Linear Factor', default_value=[1.0, 1.0, 1.0]) - self.add_input('NodeSocketVector', 'Angular') - self.add_input('NodeSocketVector', 'Angular Factor', default_value=[1.0, 1.0, 1.0]) + self.add_input('ArmVectorSocket', 'Linear') + self.add_input('ArmVectorSocket', 'Linear Factor', default_value=[1.0, 1.0, 1.0]) + self.add_input('ArmVectorSocket', 'Angular') + self.add_input('ArmVectorSocket', 'Angular Factor', default_value=[1.0, 1.0, 1.0]) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/physics/LN_set_world_gravity.py b/blender/arm/logicnode/physics/LN_set_world_gravity.py index c04fc6c4..e8646994 100644 --- a/blender/arm/logicnode/physics/LN_set_world_gravity.py +++ b/blender/arm/logicnode/physics/LN_set_world_gravity.py @@ -12,6 +12,6 @@ class SetGravityNode(ArmLogicTreeNode): def init(self, context): super(SetGravityNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketVector', 'Gravity') + self.add_input('ArmVectorSocket', 'Gravity') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/physics/LN_volume_trigger.py b/blender/arm/logicnode/physics/LN_volume_trigger.py index 240588aa..fab5aa3c 100644 --- a/blender/arm/logicnode/physics/LN_volume_trigger.py +++ b/blender/arm/logicnode/physics/LN_volume_trigger.py @@ -24,7 +24,7 @@ class VolumeTriggerNode(ArmLogicTreeNode): self.add_input('ArmNodeSocketObject', 'Object 1') self.add_input('ArmNodeSocketObject', 'Object 2') - self.add_output('NodeSocketBool', 'Bool') + self.add_output('ArmBoolSocket', 'Bool') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_get_global_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_get_global_node.py index bf1d7faf..5cf34dda 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_get_global_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_get_global_node.py @@ -9,10 +9,10 @@ class ColorgradingGetGlobalNode(ArmLogicTreeNode): def init(self, context): super(ColorgradingGetGlobalNode, self).init(context) - self.add_output('NodeSocketFloat', 'Whitebalance') - self.add_output('NodeSocketVector', 'Tint') - self.add_output('NodeSocketVector', 'Saturation') - self.add_output('NodeSocketVector', 'Contrast') - self.add_output('NodeSocketVector', 'Gamma') - self.add_output('NodeSocketVector', 'Gain') - self.add_output('NodeSocketVector', 'Offset') + self.add_output('ArmFloatSocket', 'Whitebalance') + self.add_output('ArmVectorSocket', 'Tint') + self.add_output('ArmVectorSocket', 'Saturation') + self.add_output('ArmVectorSocket', 'Contrast') + self.add_output('ArmVectorSocket', 'Gamma') + self.add_output('ArmVectorSocket', 'Gain') + self.add_output('ArmVectorSocket', 'Offset') diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_get_highlight_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_get_highlight_node.py index 598aba4f..583fe71b 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_get_highlight_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_get_highlight_node.py @@ -9,9 +9,9 @@ class ColorgradingGetHighlightNode(ArmLogicTreeNode): def init(self, context): super(ColorgradingGetHighlightNode, self).init(context) - self.add_output('NodeSocketFloat', 'HightlightMin') - self.add_output('NodeSocketVector', 'Saturation') - self.add_output('NodeSocketVector', 'Contrast') - self.add_output('NodeSocketVector', 'Gamma') - self.add_output('NodeSocketVector', 'Gain') - self.add_output('NodeSocketVector', 'Offset') + self.add_output('ArmFloatSocket', 'HightlightMin') + self.add_output('ArmVectorSocket', 'Saturation') + self.add_output('ArmVectorSocket', 'Contrast') + self.add_output('ArmVectorSocket', 'Gamma') + self.add_output('ArmVectorSocket', 'Gain') + self.add_output('ArmVectorSocket', 'Offset') diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_get_midtone_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_get_midtone_node.py index fde32c60..cd8d860d 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_get_midtone_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_get_midtone_node.py @@ -9,8 +9,8 @@ class ColorgradingGetMidtoneNode(ArmLogicTreeNode): def init(self, context): super(ColorgradingGetMidtoneNode, self).init(context) - self.add_output('NodeSocketVector', 'Saturation') - self.add_output('NodeSocketVector', 'Contrast') - self.add_output('NodeSocketVector', 'Gamma') - self.add_output('NodeSocketVector', 'Gain') - self.add_output('NodeSocketVector', 'Offset') + self.add_output('ArmVectorSocket', 'Saturation') + self.add_output('ArmVectorSocket', 'Contrast') + self.add_output('ArmVectorSocket', 'Gamma') + self.add_output('ArmVectorSocket', 'Gain') + self.add_output('ArmVectorSocket', 'Offset') diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_get_shadow_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_get_shadow_node.py index c4662185..16c96cb6 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_get_shadow_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_get_shadow_node.py @@ -9,9 +9,9 @@ class ColorgradingGetShadowNode(ArmLogicTreeNode): def init(self, context): super(ColorgradingGetShadowNode, self).init(context) - self.add_output('NodeSocketFloat', 'ShadowMax') - self.add_output('NodeSocketVector', 'Saturation') - self.add_output('NodeSocketVector', 'Contrast') - self.add_output('NodeSocketVector', 'Gamma') - self.add_output('NodeSocketVector', 'Gain') - self.add_output('NodeSocketVector', 'Offset') + self.add_output('ArmFloatSocket', 'ShadowMax') + self.add_output('ArmVectorSocket', 'Saturation') + self.add_output('ArmVectorSocket', 'Contrast') + self.add_output('ArmVectorSocket', 'Gamma') + self.add_output('ArmVectorSocket', 'Gain') + self.add_output('ArmVectorSocket', 'Offset') diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_set_global_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_set_global_node.py index ee3aaad5..c9258bb6 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_set_global_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_set_global_node.py @@ -42,22 +42,22 @@ class ColorgradingSetGlobalNode(ArmLogicTreeNode): def draw_nodes_uniform(self, context): - self.add_input('NodeSocketFloat', 'Whitebalance', default_value=6500.0) - self.add_input('NodeSocketColor', 'Tint', default_value=[1.0, 1.0, 1.0, 1.0]) - self.add_input('NodeSocketFloat', 'Saturation', default_value=1) - self.add_input('NodeSocketFloat', 'Contrast', default_value=1) - self.add_input('NodeSocketFloat', 'Gamma', default_value=1) - self.add_input('NodeSocketFloat', 'Gain', default_value=1) - self.add_input('NodeSocketFloat', 'Offset', default_value=1) + self.add_input('ArmFloatSocket', 'Whitebalance', default_value=6500.0) + self.add_input('ArmColorSocket', 'Tint', default_value=[1.0, 1.0, 1.0, 1.0]) + self.add_input('ArmFloatSocket', 'Saturation', default_value=1) + self.add_input('ArmFloatSocket', 'Contrast', default_value=1) + self.add_input('ArmFloatSocket', 'Gamma', default_value=1) + self.add_input('ArmFloatSocket', 'Gain', default_value=1) + self.add_input('ArmFloatSocket', 'Offset', default_value=1) def draw_nodes_rgb(self, context): - self.add_input('NodeSocketFloat', 'Whitebalance', default_value=6500.0) - self.add_input('NodeSocketVector', 'Tint', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Saturation', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Contrast', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Gamma', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Gain', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Offset', default_value=[1,1,1]) + self.add_input('ArmFloatSocket', 'Whitebalance', default_value=6500.0) + self.add_input('ArmVectorSocket', 'Tint', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Saturation', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Contrast', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Gamma', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Gain', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Offset', default_value=[1,1,1]) def draw_nodes_colorwheel(self, context): pass diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_set_highlight_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_set_highlight_node.py index 64144b18..bc775851 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_set_highlight_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_set_highlight_node.py @@ -42,20 +42,20 @@ class ColorgradingSetHighlightNode(ArmLogicTreeNode): def draw_nodes_uniform(self, context): - self.add_input('NodeSocketFloat', 'HighlightMin', default_value=0) - self.add_input('NodeSocketFloat', 'Saturation', default_value=1) - self.add_input('NodeSocketFloat', 'Contrast', default_value=1) - self.add_input('NodeSocketFloat', 'Gamma', default_value=1) - self.add_input('NodeSocketFloat', 'Gain', default_value=1) - self.add_input('NodeSocketFloat', 'Offset', default_value=1) + self.add_input('ArmFloatSocket', 'HighlightMin', default_value=0) + self.add_input('ArmFloatSocket', 'Saturation', default_value=1) + self.add_input('ArmFloatSocket', 'Contrast', default_value=1) + self.add_input('ArmFloatSocket', 'Gamma', default_value=1) + self.add_input('ArmFloatSocket', 'Gain', default_value=1) + self.add_input('ArmFloatSocket', 'Offset', default_value=1) def draw_nodes_rgb(self, context): - self.add_input('NodeSocketFloat', 'HighlightMin', default_value=0) - self.add_input('NodeSocketVector', 'Saturation', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Contrast', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Gamma', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Gain', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Offset', default_value=[1,1,1]) + self.add_input('ArmFloatSocket', 'HighlightMin', default_value=0) + self.add_input('ArmVectorSocket', 'Saturation', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Contrast', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Gamma', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Gain', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Offset', default_value=[1,1,1]) def draw_nodes_colorwheel(self, context): pass diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_set_midtone_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_set_midtone_node.py index 0845c625..14dcdbdf 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_set_midtone_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_set_midtone_node.py @@ -42,19 +42,19 @@ class ColorgradingSetMidtoneNode(ArmLogicTreeNode): def draw_nodes_uniform(self, context): - self.add_input('NodeSocketFloat', 'Saturation', default_value=1) - self.add_input('NodeSocketFloat', 'Contrast', default_value=1) - self.add_input('NodeSocketFloat', 'Gamma', default_value=1) - self.add_input('NodeSocketFloat', 'Gain', default_value=1) - self.add_input('NodeSocketFloat', 'Offset', default_value=1) + self.add_input('ArmFloatSocket', 'Saturation', default_value=1) + self.add_input('ArmFloatSocket', 'Contrast', default_value=1) + self.add_input('ArmFloatSocket', 'Gamma', default_value=1) + self.add_input('ArmFloatSocket', 'Gain', default_value=1) + self.add_input('ArmFloatSocket', 'Offset', default_value=1) def draw_nodes_rgb(self, context): - self.add_input('NodeSocketVector', 'Tint', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Saturation', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Contrast', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Gamma', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Gain', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Offset', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Tint', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Saturation', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Contrast', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Gamma', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Gain', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Offset', default_value=[1,1,1]) def draw_nodes_colorwheel(self, context): pass diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_set_shadow_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_set_shadow_node.py index b32f64e7..a3abd236 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_set_shadow_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_set_shadow_node.py @@ -42,20 +42,20 @@ class ColorgradingSetShadowNode(ArmLogicTreeNode): def draw_nodes_uniform(self, context): - self.add_input('NodeSocketFloat', 'ShadowMax', default_value=1) - self.add_input('NodeSocketFloat', 'Saturation', default_value=1) - self.add_input('NodeSocketFloat', 'Contrast', default_value=1) - self.add_input('NodeSocketFloat', 'Gamma', default_value=1) - self.add_input('NodeSocketFloat', 'Gain', default_value=1) - self.add_input('NodeSocketFloat', 'Offset', default_value=1) + self.add_input('ArmFloatSocket', 'ShadowMax', default_value=1) + self.add_input('ArmFloatSocket', 'Saturation', default_value=1) + self.add_input('ArmFloatSocket', 'Contrast', default_value=1) + self.add_input('ArmFloatSocket', 'Gamma', default_value=1) + self.add_input('ArmFloatSocket', 'Gain', default_value=1) + self.add_input('ArmFloatSocket', 'Offset', default_value=1) def draw_nodes_rgb(self, context): - self.add_input('NodeSocketFloat', 'ShadowMax', default_value=1) - self.add_input('NodeSocketVector', 'Saturation', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Contrast', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Gamma', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Gain', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Offset', default_value=[1,1,1]) + self.add_input('ArmFloatSocket', 'ShadowMax', default_value=1) + self.add_input('ArmVectorSocket', 'Saturation', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Contrast', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Gamma', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Gain', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Offset', default_value=[1,1,1]) def draw_nodes_colorwheel(self, context): pass diff --git a/blender/arm/logicnode/postprocess/LN_get_bloom_settings.py b/blender/arm/logicnode/postprocess/LN_get_bloom_settings.py index e1774cdb..744e7721 100644 --- a/blender/arm/logicnode/postprocess/LN_get_bloom_settings.py +++ b/blender/arm/logicnode/postprocess/LN_get_bloom_settings.py @@ -8,6 +8,6 @@ class BloomGetNode(ArmLogicTreeNode): def init(self, context): super(BloomGetNode, self).init(context) - self.add_output('NodeSocketFloat', 'Threshold') - self.add_output('NodeSocketFloat', 'Strength') - self.add_output('NodeSocketFloat', 'Radius') + self.add_output('ArmFloatSocket', 'Threshold') + self.add_output('ArmFloatSocket', 'Strength') + self.add_output('ArmFloatSocket', 'Radius') diff --git a/blender/arm/logicnode/postprocess/LN_get_ca_settings.py b/blender/arm/logicnode/postprocess/LN_get_ca_settings.py index 60a9a429..51fb624d 100644 --- a/blender/arm/logicnode/postprocess/LN_get_ca_settings.py +++ b/blender/arm/logicnode/postprocess/LN_get_ca_settings.py @@ -8,5 +8,5 @@ class ChromaticAberrationGetNode(ArmLogicTreeNode): def init(self, context): super(ChromaticAberrationGetNode, self).init(context) - self.add_output('NodeSocketFloat', 'Strength') - self.add_output('NodeSocketFloat', 'Samples') + self.add_output('ArmFloatSocket', 'Strength') + self.add_output('ArmFloatSocket', 'Samples') diff --git a/blender/arm/logicnode/postprocess/LN_get_camera_post_process.py b/blender/arm/logicnode/postprocess/LN_get_camera_post_process.py index 33599052..71906eae 100644 --- a/blender/arm/logicnode/postprocess/LN_get_camera_post_process.py +++ b/blender/arm/logicnode/postprocess/LN_get_camera_post_process.py @@ -8,13 +8,13 @@ class CameraGetNode(ArmLogicTreeNode): def init(self, context): super(CameraGetNode, self).init(context) - self.add_output('NodeSocketFloat', 'F-Stop') - self.add_output('NodeSocketFloat', 'Shutter Time') - self.add_output('NodeSocketFloat', 'ISO') - self.add_output('NodeSocketFloat', 'Exposure Compensation') - self.add_output('NodeSocketFloat', 'Fisheye Distortion') - self.add_output('NodeSocketBool', 'Auto Focus') - self.add_output('NodeSocketFloat', 'DOF Distance') - self.add_output('NodeSocketFloat', 'DOF Length') - self.add_output('NodeSocketFloat', 'DOF F-Stop') - self.add_output('NodeSocketFloat', 'Film Grain') + self.add_output('ArmFloatSocket', 'F-Stop') + self.add_output('ArmFloatSocket', 'Shutter Time') + self.add_output('ArmFloatSocket', 'ISO') + self.add_output('ArmFloatSocket', 'Exposure Compensation') + self.add_output('ArmFloatSocket', 'Fisheye Distortion') + self.add_output('ArmBoolSocket', 'Auto Focus') + self.add_output('ArmFloatSocket', 'DOF Distance') + self.add_output('ArmFloatSocket', 'DOF Length') + self.add_output('ArmFloatSocket', 'DOF F-Stop') + self.add_output('ArmFloatSocket', 'Film Grain') diff --git a/blender/arm/logicnode/postprocess/LN_get_lenstexture_settings.py b/blender/arm/logicnode/postprocess/LN_get_lenstexture_settings.py index 6f8aa6a3..fd93f516 100644 --- a/blender/arm/logicnode/postprocess/LN_get_lenstexture_settings.py +++ b/blender/arm/logicnode/postprocess/LN_get_lenstexture_settings.py @@ -8,8 +8,8 @@ class LenstextureGetNode(ArmLogicTreeNode): def init(self, context): super(LenstextureGetNode, self).init(context) - self.add_output('NodeSocketFloat', 'Center Min Clip') - self.add_output('NodeSocketFloat', 'Center Max Clip') - self.add_output('NodeSocketFloat', 'Luminance Min') - self.add_output('NodeSocketFloat', 'Luminance Max') - self.add_output('NodeSocketFloat', 'Brightness Exponent') + self.add_output('ArmFloatSocket', 'Center Min Clip') + self.add_output('ArmFloatSocket', 'Center Max Clip') + self.add_output('ArmFloatSocket', 'Luminance Min') + self.add_output('ArmFloatSocket', 'Luminance Max') + self.add_output('ArmFloatSocket', 'Brightness Exponent') diff --git a/blender/arm/logicnode/postprocess/LN_get_ssao_settings.py b/blender/arm/logicnode/postprocess/LN_get_ssao_settings.py index 3aecab2c..7208d123 100644 --- a/blender/arm/logicnode/postprocess/LN_get_ssao_settings.py +++ b/blender/arm/logicnode/postprocess/LN_get_ssao_settings.py @@ -8,6 +8,6 @@ class SSAOGetNode(ArmLogicTreeNode): def init(self, context): super(SSAOGetNode, self).init(context) - self.add_output('NodeSocketFloat', 'Radius') - self.add_output('NodeSocketFloat', 'Strength') - self.add_output('NodeSocketFloat', 'Max Steps') + self.add_output('ArmFloatSocket', 'Radius') + self.add_output('ArmFloatSocket', 'Strength') + self.add_output('ArmFloatSocket', 'Max Steps') diff --git a/blender/arm/logicnode/postprocess/LN_get_ssr_settings.py b/blender/arm/logicnode/postprocess/LN_get_ssr_settings.py index 782ce30f..69ac4405 100644 --- a/blender/arm/logicnode/postprocess/LN_get_ssr_settings.py +++ b/blender/arm/logicnode/postprocess/LN_get_ssr_settings.py @@ -8,8 +8,8 @@ class SSRGetNode(ArmLogicTreeNode): def init(self, context): super(SSRGetNode, self).init(context) - self.add_output('NodeSocketFloat', 'SSR Step') - self.add_output('NodeSocketFloat', 'SSR Step Min') - self.add_output('NodeSocketFloat', 'SSR Search') - self.add_output('NodeSocketFloat', 'SSR Falloff') - self.add_output('NodeSocketFloat', 'SSR Jitter') + self.add_output('ArmFloatSocket', 'SSR Step') + self.add_output('ArmFloatSocket', 'SSR Step Min') + self.add_output('ArmFloatSocket', 'SSR Search') + self.add_output('ArmFloatSocket', 'SSR Falloff') + self.add_output('ArmFloatSocket', 'SSR Jitter') diff --git a/blender/arm/logicnode/postprocess/LN_lenstexture_set.py b/blender/arm/logicnode/postprocess/LN_lenstexture_set.py index 2fe8a79d..4ee7380f 100644 --- a/blender/arm/logicnode/postprocess/LN_lenstexture_set.py +++ b/blender/arm/logicnode/postprocess/LN_lenstexture_set.py @@ -9,10 +9,10 @@ class LenstextureSetNode(ArmLogicTreeNode): def init(self, context): super(LenstextureSetNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketFloat', 'Center Min Clip', default_value=0.1) - self.add_input('NodeSocketFloat', 'Center Max Clip', default_value=0.5) - self.add_input('NodeSocketFloat', 'Luminance Min', default_value=0.10) - self.add_input('NodeSocketFloat', 'Luminance Max', default_value=2.50) - self.add_input('NodeSocketFloat', 'Brightness Exponent', default_value=2.0) + self.add_input('ArmFloatSocket', 'Center Min Clip', default_value=0.1) + self.add_input('ArmFloatSocket', 'Center Max Clip', default_value=0.5) + self.add_input('ArmFloatSocket', 'Luminance Min', default_value=0.10) + self.add_input('ArmFloatSocket', 'Luminance Max', default_value=2.50) + self.add_input('ArmFloatSocket', 'Brightness Exponent', default_value=2.0) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/postprocess/LN_set_bloom_settings.py b/blender/arm/logicnode/postprocess/LN_set_bloom_settings.py index ffa85450..90c8d5a3 100644 --- a/blender/arm/logicnode/postprocess/LN_set_bloom_settings.py +++ b/blender/arm/logicnode/postprocess/LN_set_bloom_settings.py @@ -9,8 +9,8 @@ class BloomSetNode(ArmLogicTreeNode): def init(self, context): super(BloomSetNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketFloat', 'Threshold', default_value=1.00) - self.add_input('NodeSocketFloat', 'Strength', default_value=3.50) - self.add_input('NodeSocketFloat', 'Radius', default_value=3.0) + self.add_input('ArmFloatSocket', 'Threshold', default_value=1.00) + self.add_input('ArmFloatSocket', 'Strength', default_value=3.50) + self.add_input('ArmFloatSocket', 'Radius', default_value=3.0) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/postprocess/LN_set_ca_settings.py b/blender/arm/logicnode/postprocess/LN_set_ca_settings.py index 18b6f0a1..be459419 100644 --- a/blender/arm/logicnode/postprocess/LN_set_ca_settings.py +++ b/blender/arm/logicnode/postprocess/LN_set_ca_settings.py @@ -9,7 +9,7 @@ class ChromaticAberrationSetNode(ArmLogicTreeNode): def init(self, context): super(ChromaticAberrationSetNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketFloat', 'Strength', default_value=2.0) - self.add_input('NodeSocketInt', 'Samples', default_value=32) + self.add_input('ArmFloatSocket', 'Strength', default_value=2.0) + self.add_input('ArmIntSocket', 'Samples', default_value=32) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/postprocess/LN_set_camera_post_process.py b/blender/arm/logicnode/postprocess/LN_set_camera_post_process.py index a21d8ddc..e589b6c9 100644 --- a/blender/arm/logicnode/postprocess/LN_set_camera_post_process.py +++ b/blender/arm/logicnode/postprocess/LN_set_camera_post_process.py @@ -9,16 +9,16 @@ class CameraSetNode(ArmLogicTreeNode): def init(self, context): super(CameraSetNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketFloat', 'F-stop', default_value=2.0) - self.add_input('NodeSocketFloat', 'Shutter Time', default_value=1.0) - self.add_input('NodeSocketFloat', 'ISO', default_value=100.0) - self.add_input('NodeSocketFloat', 'Exposure Compensation', default_value=0.0) - self.add_input('NodeSocketFloat', 'Fisheye Distortion', default_value=0.01) - self.add_input('NodeSocketBool', 'Auto Focus', default_value=True) - self.add_input('NodeSocketFloat', 'DoF Distance', default_value=10.0) - self.add_input('NodeSocketFloat', 'DoF Length', default_value=160.0) - self.add_input('NodeSocketFloat', 'DoF F-Stop', default_value=128.0) - self.add_input('NodeSocketInt', 'Tonemapper', default_value=0.0) - self.add_input('NodeSocketFloat', 'Film Grain', default_value=2.0) + self.add_input('ArmFloatSocket', 'F-stop', default_value=2.0) + self.add_input('ArmFloatSocket', 'Shutter Time', default_value=1.0) + self.add_input('ArmFloatSocket', 'ISO', default_value=100.0) + self.add_input('ArmFloatSocket', 'Exposure Compensation', default_value=0.0) + self.add_input('ArmFloatSocket', 'Fisheye Distortion', default_value=0.01) + self.add_input('ArmBoolSocket', 'Auto Focus', default_value=True) + self.add_input('ArmFloatSocket', 'DoF Distance', default_value=10.0) + self.add_input('ArmFloatSocket', 'DoF Length', default_value=160.0) + self.add_input('ArmFloatSocket', 'DoF F-Stop', default_value=128.0) + self.add_input('ArmIntSocket', 'Tonemapper', default_value=0.0) + self.add_input('ArmFloatSocket', 'Film Grain', default_value=2.0) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/postprocess/LN_set_ssao_settings.py b/blender/arm/logicnode/postprocess/LN_set_ssao_settings.py index 199d5d6c..386e980b 100644 --- a/blender/arm/logicnode/postprocess/LN_set_ssao_settings.py +++ b/blender/arm/logicnode/postprocess/LN_set_ssao_settings.py @@ -9,8 +9,8 @@ class SSAOSetNode(ArmLogicTreeNode): def init(self, context): super(SSAOSetNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketFloat', 'Radius', default_value=1.0) - self.add_input('NodeSocketFloat', 'Strength', default_value=5.0) - self.add_input('NodeSocketInt', 'Max Steps', default_value=8) + self.add_input('ArmFloatSocket', 'Radius', default_value=1.0) + self.add_input('ArmFloatSocket', 'Strength', default_value=5.0) + self.add_input('ArmIntSocket', 'Max Steps', default_value=8) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/postprocess/LN_set_ssr_settings.py b/blender/arm/logicnode/postprocess/LN_set_ssr_settings.py index 40995378..fd6bb111 100644 --- a/blender/arm/logicnode/postprocess/LN_set_ssr_settings.py +++ b/blender/arm/logicnode/postprocess/LN_set_ssr_settings.py @@ -9,10 +9,10 @@ class SSRSetNode(ArmLogicTreeNode): def init(self, context): super(SSRSetNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketFloat', 'SSR Step', default_value=0.04) - self.add_input('NodeSocketFloat', 'SSR Step Min', default_value=0.05) - self.add_input('NodeSocketFloat', 'SSR Search', default_value=5.0) - self.add_input('NodeSocketFloat', 'SSR Falloff', default_value=5.0) - self.add_input('NodeSocketFloat', 'SSR Jitter', default_value=0.6) + self.add_input('ArmFloatSocket', 'SSR Step', default_value=0.04) + self.add_input('ArmFloatSocket', 'SSR Step Min', default_value=0.05) + self.add_input('ArmFloatSocket', 'SSR Search', default_value=5.0) + self.add_input('ArmFloatSocket', 'SSR Falloff', default_value=5.0) + self.add_input('ArmFloatSocket', 'SSR Jitter', default_value=0.6) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/random/LN_random_boolean.py b/blender/arm/logicnode/random/LN_random_boolean.py index 8fef83ae..039a2c91 100644 --- a/blender/arm/logicnode/random/LN_random_boolean.py +++ b/blender/arm/logicnode/random/LN_random_boolean.py @@ -9,4 +9,4 @@ class RandomBooleanNode(ArmLogicTreeNode): def init(self, context): super(RandomBooleanNode, self).init(context) - self.add_output('NodeSocketBool', 'Bool') + self.add_output('ArmBoolSocket', 'Bool') diff --git a/blender/arm/logicnode/random/LN_random_choice.py b/blender/arm/logicnode/random/LN_random_choice.py index ad6b4f7e..ee273513 100644 --- a/blender/arm/logicnode/random/LN_random_choice.py +++ b/blender/arm/logicnode/random/LN_random_choice.py @@ -11,4 +11,4 @@ class RandomChoiceNode(ArmLogicTreeNode): super().init(context) self.add_input('ArmNodeSocketArray', 'Array') - self.add_output('NodeSocketShader', 'Value') + self.add_output('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/random/LN_random_color.py b/blender/arm/logicnode/random/LN_random_color.py index 82ef9bf8..38c36442 100644 --- a/blender/arm/logicnode/random/LN_random_color.py +++ b/blender/arm/logicnode/random/LN_random_color.py @@ -9,4 +9,4 @@ class RandomColorNode(ArmLogicTreeNode): def init(self, context): super(RandomColorNode, self).init(context) - self.add_output('NodeSocketColor', 'Color') + self.add_output('ArmColorSocket', 'Color') diff --git a/blender/arm/logicnode/random/LN_random_float.py b/blender/arm/logicnode/random/LN_random_float.py index 6303d5e0..22dca241 100644 --- a/blender/arm/logicnode/random/LN_random_float.py +++ b/blender/arm/logicnode/random/LN_random_float.py @@ -9,7 +9,7 @@ class RandomFloatNode(ArmLogicTreeNode): def init(self, context): super(RandomFloatNode, self).init(context) - self.add_input('NodeSocketFloat', 'Min') - self.add_input('NodeSocketFloat', 'Max', default_value=1.0) - # self.add_input('NodeSocketInt', 'Seed') - self.add_output('NodeSocketFloat', 'Float') + self.add_input('ArmFloatSocket', 'Min') + self.add_input('ArmFloatSocket', 'Max', default_value=1.0) + # self.add_input('ArmIntSocket', 'Seed') + self.add_output('ArmFloatSocket', 'Float') diff --git a/blender/arm/logicnode/random/LN_random_integer.py b/blender/arm/logicnode/random/LN_random_integer.py index dfb3442f..0b69d5a7 100644 --- a/blender/arm/logicnode/random/LN_random_integer.py +++ b/blender/arm/logicnode/random/LN_random_integer.py @@ -9,6 +9,6 @@ class RandomIntegerNode(ArmLogicTreeNode): def init(self, context): super(RandomIntegerNode, self).init(context) - self.add_input('NodeSocketInt', 'Min') - self.add_input('NodeSocketInt', 'Max', default_value=2) - self.add_output('NodeSocketInt', 'Int') + self.add_input('ArmIntSocket', 'Min') + self.add_input('ArmIntSocket', 'Max', default_value=2) + self.add_output('ArmIntSocket', 'Int') diff --git a/blender/arm/logicnode/random/LN_random_vector.py b/blender/arm/logicnode/random/LN_random_vector.py index 6986a8a0..11161f8e 100644 --- a/blender/arm/logicnode/random/LN_random_vector.py +++ b/blender/arm/logicnode/random/LN_random_vector.py @@ -9,6 +9,6 @@ class RandomVectorNode(ArmLogicTreeNode): def init(self, context): super(RandomVectorNode, self).init(context) - self.add_input('NodeSocketVector', 'Min', default_value=[-1.0, -1.0, -1.0]) - self.add_input('NodeSocketVector', 'Max', default_value=[1.0, 1.0, 1.0]) - self.add_output('NodeSocketVector', 'Vector') + self.add_input('ArmVectorSocket', 'Min', default_value=[-1.0, -1.0, -1.0]) + self.add_input('ArmVectorSocket', 'Max', default_value=[1.0, 1.0, 1.0]) + self.add_output('ArmVectorSocket', 'Vector') diff --git a/blender/arm/logicnode/renderpath/LN_set_post_process_quality.py b/blender/arm/logicnode/renderpath/LN_set_post_process_quality.py index e7b6e31e..a2d01c1b 100644 --- a/blender/arm/logicnode/renderpath/LN_set_post_process_quality.py +++ b/blender/arm/logicnode/renderpath/LN_set_post_process_quality.py @@ -18,7 +18,7 @@ class RpConfigNode(ArmLogicTreeNode): def init(self, context): super(RpConfigNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketBool', 'Enable') + self.add_input('ArmBoolSocket', 'Enable') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/renderpath/LN_set_shader_uniform.py b/blender/arm/logicnode/renderpath/LN_set_shader_uniform.py index cf34577e..7de5ccfe 100644 --- a/blender/arm/logicnode/renderpath/LN_set_shader_uniform.py +++ b/blender/arm/logicnode/renderpath/LN_set_shader_uniform.py @@ -15,11 +15,11 @@ class SetShaderUniformNode(ArmLogicTreeNode): self.inputs.remove(self.inputs[2]) if self.property0 == 'int': - self.add_input('NodeSocketInt', 'Int') + self.add_input('ArmIntSocket', 'Int') elif self.property0 == 'float': - self.add_input('NodeSocketFloat', 'Float') + self.add_input('ArmFloatSocket', 'Float') elif self.property0 in ('vec2', 'vec3', 'vec4'): - self.add_input('NodeSocketVector', 'Vector') + self.add_input('ArmVectorSocket', 'Vector') property0: HaxeEnumProperty( 'property0', @@ -36,8 +36,8 @@ class SetShaderUniformNode(ArmLogicTreeNode): def init(self, context): super().init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Uniform Name') - self.add_input('NodeSocketFloat', 'Float') + self.add_input('ArmStringSocket', 'Uniform Name') + self.add_input('ArmFloatSocket', 'Float') self.add_output('ArmNodeSocketAction', 'Out') def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/replacement.py b/blender/arm/logicnode/replacement.py index 99585f13..c01556a6 100644 --- a/blender/arm/logicnode/replacement.py +++ b/blender/arm/logicnode/replacement.py @@ -10,6 +10,7 @@ Original author: @niacdoial import os.path import time import traceback +import typing from typing import Dict, List, Optional, Tuple import bpy.props @@ -17,6 +18,7 @@ import bpy.props import arm.log as log import arm.logicnode.arm_nodes as arm_nodes import arm.logicnode.arm_sockets +import arm.node_utils as node_utils # List of errors that occurred during the replacement # Format: (error identifier, node.bl_idname (or None), tree name, exception traceback (optional)) @@ -272,3 +274,78 @@ def replace_all(): log.error(f'There were errors in the node update procedure, a detailed report has been written to {reportfile}') bpy.ops.arm.show_node_update_errors() + + +def node_compat_sdk2108(): + """SDK 21.08 broke compatibility with older nodes as nodes now use + custom sockets even for Blender's default data types and custom + property "constructors". This allows to listen for events for the + live patch system. + + In order to update older nodes this routine is used. It creates a + full copy of the nodes and replaces all properties and sockets with + their new equivalents. + """ + for tree in bpy.data.node_groups: + if tree.bl_idname == "ArmLogicTreeType": + for node in list(tree.nodes): + # Don't raise exceptions for invalid unregistered nodes, this + # function didn't cause the registration problem if there is one + if not node.__class__.is_registered_node_type(): + continue + + newnode = tree.nodes.new(node.__class__.bl_idname) + + newnode.parent = node.parent + newnode.location = node.location + newnode.select = node.select + + # Also copy the node's version number to _not_ prevent actual node + # replacement after this step + newnode.arm_version = node.arm_version + + # First replace all properties + for prop_name, prop in typing.get_type_hints(node.__class__, {}, {}).items(): + if isinstance(prop, bpy.props._PropertyDeferred): + if hasattr(node, prop_name) and hasattr(newnode, prop_name): + setattr(newnode, prop_name, getattr(node, prop_name)) + + # Replace sockets with new socket types + socket_replacements = { + 'NodeSocketBool': 'ArmBoolSocket', + 'NodeSocketColor': 'ArmColorSocket', + 'NodeSocketFloat': 'ArmFloatSocket', + 'NodeSocketInt': 'ArmIntSocket', + 'NodeSocketShader': 'ArmDynamicSocket', + 'NodeSocketString': 'ArmStringSocket', + 'NodeSocketVector': 'ArmVectorSocket' + } + + # Recreate all sockets + newnode.inputs.clear() + for inp in node.inputs: + inp_idname = inp.bl_idname + inp_idname = socket_replacements.get(inp_idname, inp_idname) + + newinp = newnode.inputs.new(inp_idname, inp.name, identifier=inp.identifier) + + if inp.is_linked: + for link in inp.links: + tree.links.new(link.from_socket, newinp) + else: + node_utils.set_socket_default(newinp, node_utils.get_socket_default(inp)) + + newnode.outputs.clear() + for out in node.outputs: + out_idname = out.bl_idname + out_idname = socket_replacements.get(out_idname, out_idname) + + newout = newnode.outputs.new(out_idname, out.name, identifier=out.identifier) + + if out.is_linked: + for link in out.links: + tree.links.new(newout, link.to_socket) + else: + node_utils.set_socket_default(newout, node_utils.get_socket_default(out)) + + tree.nodes.remove(node) diff --git a/blender/arm/logicnode/scene/LN_create_collection.py b/blender/arm/logicnode/scene/LN_create_collection.py index 9c728c21..5e6157c3 100644 --- a/blender/arm/logicnode/scene/LN_create_collection.py +++ b/blender/arm/logicnode/scene/LN_create_collection.py @@ -10,6 +10,6 @@ class CreateCollectionNode(ArmLogicTreeNode): def init(self, context): super(CreateCollectionNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Collection') + self.add_input('ArmStringSocket', 'Collection') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/scene/LN_get_collection.py b/blender/arm/logicnode/scene/LN_get_collection.py index 14fb5d7a..2048650e 100644 --- a/blender/arm/logicnode/scene/LN_get_collection.py +++ b/blender/arm/logicnode/scene/LN_get_collection.py @@ -12,6 +12,6 @@ class GetGroupNode(ArmLogicTreeNode): def init(self, context): super(GetGroupNode, self).init(context) - self.add_input('NodeSocketString', 'Name') + self.add_input('ArmStringSocket', 'Name') self.add_output('ArmNodeSocketArray', 'Objects') diff --git a/blender/arm/logicnode/scene/LN_get_scene_active.py b/blender/arm/logicnode/scene/LN_get_scene_active.py index a263b428..841a3fe1 100644 --- a/blender/arm/logicnode/scene/LN_get_scene_active.py +++ b/blender/arm/logicnode/scene/LN_get_scene_active.py @@ -8,4 +8,4 @@ class ActiveSceneNode(ArmLogicTreeNode): def init(self, context): super(ActiveSceneNode, self).init(context) - self.add_output('NodeSocketShader', 'Scene') + self.add_output('ArmDynamicSocket', 'Scene') diff --git a/blender/arm/logicnode/scene/LN_remove_collection.py b/blender/arm/logicnode/scene/LN_remove_collection.py index b7a40ce9..3e6d49c5 100644 --- a/blender/arm/logicnode/scene/LN_remove_collection.py +++ b/blender/arm/logicnode/scene/LN_remove_collection.py @@ -10,6 +10,6 @@ class RemoveGroupNode(ArmLogicTreeNode): def init(self, context): super(RemoveGroupNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Collection') + self.add_input('ArmStringSocket', 'Collection') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/scene/LN_scene.py b/blender/arm/logicnode/scene/LN_scene.py index 4131608a..d2ed0b60 100644 --- a/blender/arm/logicnode/scene/LN_scene.py +++ b/blender/arm/logicnode/scene/LN_scene.py @@ -13,7 +13,7 @@ class SceneNode(ArmLogicTreeNode): def init(self, context): super(SceneNode, self).init(context) - self.add_output('NodeSocketShader', 'Scene') + self.add_output('ArmDynamicSocket', 'Scene') def draw_buttons(self, context, layout): layout.prop_search(self, 'property0_get', bpy.data, 'scenes', icon='NONE', text='') diff --git a/blender/arm/logicnode/scene/LN_set_scene_active.py b/blender/arm/logicnode/scene/LN_set_scene_active.py index 4a231d83..e839765c 100644 --- a/blender/arm/logicnode/scene/LN_set_scene_active.py +++ b/blender/arm/logicnode/scene/LN_set_scene_active.py @@ -9,7 +9,7 @@ class SetSceneNode(ArmLogicTreeNode): def init(self, context): super(SetSceneNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Scene') + self.add_input('ArmDynamicSocket', 'Scene') self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmNodeSocketObject', 'Root') diff --git a/blender/arm/logicnode/scene/LN_spawn_collection.py b/blender/arm/logicnode/scene/LN_spawn_collection.py index 12e995e9..72c6db7c 100644 --- a/blender/arm/logicnode/scene/LN_spawn_collection.py +++ b/blender/arm/logicnode/scene/LN_spawn_collection.py @@ -33,7 +33,7 @@ class SpawnCollectionNode(ArmLogicTreeNode): def init(self, context): super(SpawnCollectionNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Transform') + self.add_input('ArmDynamicSocket', 'Transform') self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmNodeSocketArray', 'Top-Level Objects') diff --git a/blender/arm/logicnode/scene/LN_spawn_scene.py b/blender/arm/logicnode/scene/LN_spawn_scene.py index d9d42a2b..5a2b50dd 100644 --- a/blender/arm/logicnode/scene/LN_spawn_scene.py +++ b/blender/arm/logicnode/scene/LN_spawn_scene.py @@ -9,8 +9,8 @@ class SpawnSceneNode(ArmLogicTreeNode): def init(self, context): super(SpawnSceneNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Scene') - self.add_input('NodeSocketShader', 'Transform') + self.add_input('ArmDynamicSocket', 'Scene') + self.add_input('ArmDynamicSocket', 'Transform') self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmNodeSocketObject', 'Root') diff --git a/blender/arm/logicnode/string/LN_concatenate_string.py b/blender/arm/logicnode/string/LN_concatenate_string.py index 381957c0..0c8c3e13 100644 --- a/blender/arm/logicnode/string/LN_concatenate_string.py +++ b/blender/arm/logicnode/string/LN_concatenate_string.py @@ -12,14 +12,14 @@ class ConcatenateStringNode(ArmLogicTreeNode): def init(self, context): super(ConcatenateStringNode, self).init(context) - self.add_input('NodeSocketString', 'Input 0') + self.add_input('ArmStringSocket', 'Input 0') - self.add_output('NodeSocketString', 'String') + self.add_output('ArmStringSocket', 'String') def draw_buttons(self, context, layout): row = layout.row(align=True) op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketString' + op.socket_type = 'ArmStringSocket' op = row.operator('arm.node_remove_input', text='', icon='X', emboss=True) op.node_index = str(id(self)) diff --git a/blender/arm/logicnode/string/LN_parse_float.py b/blender/arm/logicnode/string/LN_parse_float.py index 2cb6b8cb..f52fc698 100644 --- a/blender/arm/logicnode/string/LN_parse_float.py +++ b/blender/arm/logicnode/string/LN_parse_float.py @@ -9,6 +9,6 @@ class ParseFloatNode(ArmLogicTreeNode): def init(self, context): super(ParseFloatNode, self).init(context) - self.add_output('NodeSocketFloat', 'Float') + self.add_output('ArmFloatSocket', 'Float') - self.add_input('NodeSocketString', 'String') + self.add_input('ArmStringSocket', 'String') diff --git a/blender/arm/logicnode/string/LN_split_string.py b/blender/arm/logicnode/string/LN_split_string.py index 793716eb..79941b81 100644 --- a/blender/arm/logicnode/string/LN_split_string.py +++ b/blender/arm/logicnode/string/LN_split_string.py @@ -10,5 +10,5 @@ class SplitStringNode(ArmLogicTreeNode): super(SplitStringNode, self).init(context) self.add_output('ArmNodeSocketArray', 'Array') - self.add_input('NodeSocketString', 'String') - self.add_input('NodeSocketString', 'Split') + self.add_input('ArmStringSocket', 'String') + self.add_input('ArmStringSocket', 'Split') diff --git a/blender/arm/logicnode/string/LN_string.py b/blender/arm/logicnode/string/LN_string.py index bc37a715..6b4679c3 100644 --- a/blender/arm/logicnode/string/LN_string.py +++ b/blender/arm/logicnode/string/LN_string.py @@ -8,6 +8,6 @@ class StringNode(ArmLogicTreeNode): def init(self, context): super(StringNode, self).init(context) - self.add_input('NodeSocketString', 'String In') + self.add_input('ArmStringSocket', 'String In') - self.add_output('NodeSocketString', 'String Out', is_var=True) + self.add_output('ArmStringSocket', 'String Out', is_var=True) diff --git a/blender/arm/logicnode/string/LN_string_case.py b/blender/arm/logicnode/string/LN_string_case.py index 64d35ae1..195bb6c1 100644 --- a/blender/arm/logicnode/string/LN_string_case.py +++ b/blender/arm/logicnode/string/LN_string_case.py @@ -14,9 +14,9 @@ class CaseStringNode(ArmLogicTreeNode): def init(self, context): super(CaseStringNode, self).init(context) - self.add_input('NodeSocketString', 'String In') + self.add_input('ArmStringSocket', 'String In') - self.add_output('NodeSocketString', 'String Out') + self.add_output('ArmStringSocket', 'String Out') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/string/LN_string_contains.py b/blender/arm/logicnode/string/LN_string_contains.py index dafe546f..aa657ec5 100644 --- a/blender/arm/logicnode/string/LN_string_contains.py +++ b/blender/arm/logicnode/string/LN_string_contains.py @@ -15,10 +15,10 @@ class ContainsStringNode(ArmLogicTreeNode): def init(self, context): super(ContainsStringNode, self).init(context) - self.add_input('NodeSocketString', 'String') - self.add_input('NodeSocketString', 'Find') + self.add_input('ArmStringSocket', 'String') + self.add_input('ArmStringSocket', 'Find') - self.add_output('NodeSocketBool', 'Contains') + self.add_output('ArmBoolSocket', 'Contains') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/string/LN_string_length.py b/blender/arm/logicnode/string/LN_string_length.py index 2e4f249d..9037a1ed 100644 --- a/blender/arm/logicnode/string/LN_string_length.py +++ b/blender/arm/logicnode/string/LN_string_length.py @@ -8,6 +8,6 @@ class LengthStringNode(ArmLogicTreeNode): def init(self, context): super(LengthStringNode, self).init(context) - self.add_output('NodeSocketInt', 'Length') + self.add_output('ArmIntSocket', 'Length') - self.add_input('NodeSocketString', 'String') + self.add_input('ArmStringSocket', 'String') diff --git a/blender/arm/logicnode/string/LN_sub_string.py b/blender/arm/logicnode/string/LN_sub_string.py index 60c65213..b3b0a7c0 100644 --- a/blender/arm/logicnode/string/LN_sub_string.py +++ b/blender/arm/logicnode/string/LN_sub_string.py @@ -8,8 +8,8 @@ class SubStringNode(ArmLogicTreeNode): def init(self, context): super(SubStringNode, self).init(context) - self.add_input('NodeSocketString', 'String In') - self.add_input('NodeSocketInt', 'Start') - self.add_input('NodeSocketInt', 'End') + self.add_input('ArmStringSocket', 'String In') + self.add_input('ArmIntSocket', 'Start') + self.add_input('ArmIntSocket', 'End') - self.add_output('NodeSocketString', 'String Out') + self.add_output('ArmStringSocket', 'String Out') diff --git a/blender/arm/logicnode/trait/LN_add_trait_to_object.py b/blender/arm/logicnode/trait/LN_add_trait_to_object.py index 04a22b31..c2fe8847 100644 --- a/blender/arm/logicnode/trait/LN_add_trait_to_object.py +++ b/blender/arm/logicnode/trait/LN_add_trait_to_object.py @@ -10,6 +10,6 @@ class AddTraitNode(ArmLogicTreeNode): super(AddTraitNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketShader', 'Trait') + self.add_input('ArmDynamicSocket', 'Trait') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/trait/LN_get_object_trait.py b/blender/arm/logicnode/trait/LN_get_object_trait.py index 86c9dd15..7014a2f8 100644 --- a/blender/arm/logicnode/trait/LN_get_object_trait.py +++ b/blender/arm/logicnode/trait/LN_get_object_trait.py @@ -10,6 +10,6 @@ class GetTraitNode(ArmLogicTreeNode): def init(self, context): super(GetTraitNode, self).init(context) self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketString', 'Name') + self.add_input('ArmStringSocket', 'Name') - self.add_output('NodeSocketShader', 'Trait') + self.add_output('ArmDynamicSocket', 'Trait') diff --git a/blender/arm/logicnode/trait/LN_get_trait_name.py b/blender/arm/logicnode/trait/LN_get_trait_name.py index 49143b6b..c8156da5 100644 --- a/blender/arm/logicnode/trait/LN_get_trait_name.py +++ b/blender/arm/logicnode/trait/LN_get_trait_name.py @@ -8,7 +8,7 @@ class GetTraitNameNode(ArmLogicTreeNode): def init(self, context): super(GetTraitNameNode, self).init(context) - self.add_input('NodeSocketShader', 'Trait') + self.add_input('ArmDynamicSocket', 'Trait') - self.add_output('NodeSocketString', 'Name') - self.add_output('NodeSocketString', 'Class Type') + self.add_output('ArmStringSocket', 'Name') + self.add_output('ArmStringSocket', 'Class Type') diff --git a/blender/arm/logicnode/trait/LN_get_trait_paused.py b/blender/arm/logicnode/trait/LN_get_trait_paused.py index 2150523d..286df85d 100644 --- a/blender/arm/logicnode/trait/LN_get_trait_paused.py +++ b/blender/arm/logicnode/trait/LN_get_trait_paused.py @@ -8,6 +8,6 @@ class GetTraitPausedNode(ArmLogicTreeNode): def init(self, context): super(GetTraitPausedNode, self).init(context) - self.add_input('NodeSocketShader', 'Trait') + self.add_input('ArmDynamicSocket', 'Trait') - self.add_output('NodeSocketBool', 'Is Paused') + self.add_output('ArmBoolSocket', 'Is Paused') diff --git a/blender/arm/logicnode/trait/LN_remove_trait.py b/blender/arm/logicnode/trait/LN_remove_trait.py index 07a41f16..0d40e82a 100644 --- a/blender/arm/logicnode/trait/LN_remove_trait.py +++ b/blender/arm/logicnode/trait/LN_remove_trait.py @@ -9,6 +9,6 @@ class RemoveTraitNode(ArmLogicTreeNode): def init(self, context): super(RemoveTraitNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Trait') + self.add_input('ArmDynamicSocket', 'Trait') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/trait/LN_self_trait.py b/blender/arm/logicnode/trait/LN_self_trait.py index dc06e0c4..5e3c85b7 100644 --- a/blender/arm/logicnode/trait/LN_self_trait.py +++ b/blender/arm/logicnode/trait/LN_self_trait.py @@ -8,4 +8,4 @@ class SelfTraitNode(ArmLogicTreeNode): def init(self, context): super(SelfTraitNode, self).init(context) - self.add_output('NodeSocketShader', 'Trait') + self.add_output('ArmDynamicSocket', 'Trait') diff --git a/blender/arm/logicnode/trait/LN_set_trait_paused.py b/blender/arm/logicnode/trait/LN_set_trait_paused.py index 81ada9c4..6c4b595c 100644 --- a/blender/arm/logicnode/trait/LN_set_trait_paused.py +++ b/blender/arm/logicnode/trait/LN_set_trait_paused.py @@ -9,7 +9,7 @@ class SetTraitPausedNode(ArmLogicTreeNode): def init(self, context): super(SetTraitPausedNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Trait') - self.add_input('NodeSocketBool', 'Paused') + self.add_input('ArmDynamicSocket', 'Trait') + self.add_input('ArmBoolSocket', 'Paused') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/trait/LN_trait.py b/blender/arm/logicnode/trait/LN_trait.py index b572923f..4ed2a6dd 100644 --- a/blender/arm/logicnode/trait/LN_trait.py +++ b/blender/arm/logicnode/trait/LN_trait.py @@ -12,7 +12,7 @@ class TraitNode(ArmLogicTreeNode): def init(self, context): super(TraitNode, self).init(context) - self.add_output('NodeSocketShader', 'Trait', is_var=True) + self.add_output('ArmDynamicSocket', 'Trait', is_var=True) def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/transform/LN_append_transform.py b/blender/arm/logicnode/transform/LN_append_transform.py index a0def018..81a087c0 100644 --- a/blender/arm/logicnode/transform/LN_append_transform.py +++ b/blender/arm/logicnode/transform/LN_append_transform.py @@ -10,6 +10,6 @@ class AppendTransformNode(ArmLogicTreeNode): super(AppendTransformNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketShader', 'Transform') + self.add_input('ArmDynamicSocket', 'Transform') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/transform/LN_get_object_location.py b/blender/arm/logicnode/transform/LN_get_object_location.py index c708ba9a..06b15234 100644 --- a/blender/arm/logicnode/transform/LN_get_object_location.py +++ b/blender/arm/logicnode/transform/LN_get_object_location.py @@ -11,4 +11,4 @@ class GetLocationNode(ArmLogicTreeNode): super(GetLocationNode, self).init(context) self.add_input('ArmNodeSocketObject', 'Object') - self.add_output('NodeSocketVector', 'Location') + self.add_output('ArmVectorSocket', 'Location') diff --git a/blender/arm/logicnode/transform/LN_get_object_rotation.py b/blender/arm/logicnode/transform/LN_get_object_rotation.py index ab14b0d0..821a47d8 100644 --- a/blender/arm/logicnode/transform/LN_get_object_rotation.py +++ b/blender/arm/logicnode/transform/LN_get_object_rotation.py @@ -11,9 +11,9 @@ class GetRotationNode(ArmLogicTreeNode): super(GetRotationNode, self).init(context) self.add_input('ArmNodeSocketObject', 'Object') - self.add_output('NodeSocketVector', 'Euler Angles') - self.add_output('NodeSocketVector', 'Vector') - self.add_output('NodeSocketFloat', 'Angle (Radians)') - self.add_output('NodeSocketFloat', 'Angle (Degrees)') - self.add_output('NodeSocketVector', 'Quaternion XYZ') - self.add_output('NodeSocketFloat', 'Quaternion W') + self.add_output('ArmVectorSocket', 'Euler Angles') + self.add_output('ArmVectorSocket', 'Vector') + self.add_output('ArmFloatSocket', 'Angle (Radians)') + self.add_output('ArmFloatSocket', 'Angle (Degrees)') + self.add_output('ArmVectorSocket', 'Quaternion XYZ') + self.add_output('ArmFloatSocket', 'Quaternion W') diff --git a/blender/arm/logicnode/transform/LN_get_object_scale.py b/blender/arm/logicnode/transform/LN_get_object_scale.py index 60f21e65..f6f1ff56 100644 --- a/blender/arm/logicnode/transform/LN_get_object_scale.py +++ b/blender/arm/logicnode/transform/LN_get_object_scale.py @@ -11,4 +11,4 @@ class GetScaleNode(ArmLogicTreeNode): super(GetScaleNode, self).init(context) self.add_input('ArmNodeSocketObject', 'Object') - self.add_output('NodeSocketVector', 'Scale') + self.add_output('ArmVectorSocket', 'Scale') diff --git a/blender/arm/logicnode/transform/LN_get_object_transform.py b/blender/arm/logicnode/transform/LN_get_object_transform.py index 1477d899..a10163ae 100644 --- a/blender/arm/logicnode/transform/LN_get_object_transform.py +++ b/blender/arm/logicnode/transform/LN_get_object_transform.py @@ -12,4 +12,4 @@ class GetTransformNode(ArmLogicTreeNode): super(GetTransformNode, self).init(context) self.add_input('ArmNodeSocketObject', 'Object') - self.add_output('NodeSocketShader', 'Transform') + self.add_output('ArmDynamicSocket', 'Transform') diff --git a/blender/arm/logicnode/transform/LN_get_world_orientation.py b/blender/arm/logicnode/transform/LN_get_world_orientation.py index 7efce5b3..28c074c6 100644 --- a/blender/arm/logicnode/transform/LN_get_world_orientation.py +++ b/blender/arm/logicnode/transform/LN_get_world_orientation.py @@ -18,7 +18,7 @@ class GetWorldNode(ArmLogicTreeNode): super(GetWorldNode, self).init(context) self.add_input('ArmNodeSocketObject', 'Object') - self.add_output('NodeSocketVector', 'Vector') + self.add_output('ArmVectorSocket', 'Vector') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/transform/LN_look_at.py b/blender/arm/logicnode/transform/LN_look_at.py index 0d276883..a38b20f2 100644 --- a/blender/arm/logicnode/transform/LN_look_at.py +++ b/blender/arm/logicnode/transform/LN_look_at.py @@ -19,10 +19,10 @@ class LookAtNode(ArmLogicTreeNode): def init(self, context): super(LookAtNode, self).init(context) - self.add_input('NodeSocketVector', 'From Location') - self.add_input('NodeSocketVector', 'To Location') + self.add_input('ArmVectorSocket', 'From Location') + self.add_input('ArmVectorSocket', 'To Location') - self.add_output('NodeSocketVector', 'Rotation') + self.add_output('ArmVectorSocket', 'Rotation') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/transform/LN_rotate_object.py b/blender/arm/logicnode/transform/LN_rotate_object.py index 66cc6498..773c245f 100644 --- a/blender/arm/logicnode/transform/LN_rotate_object.py +++ b/blender/arm/logicnode/transform/LN_rotate_object.py @@ -11,8 +11,8 @@ class RotateObjectNode(ArmLogicTreeNode): super().init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketVector', 'Euler Angles') - self.add_input('NodeSocketFloat', 'Angle / W') + self.add_input('ArmVectorSocket', 'Euler Angles') + self.add_input('ArmFloatSocket', 'Angle / W') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/transform/LN_separate_transform.py b/blender/arm/logicnode/transform/LN_separate_transform.py index 5c834992..28762a17 100644 --- a/blender/arm/logicnode/transform/LN_separate_transform.py +++ b/blender/arm/logicnode/transform/LN_separate_transform.py @@ -8,8 +8,8 @@ class SeparateTransformNode(ArmLogicTreeNode): def init(self, context): super(SeparateTransformNode, self).init(context) - self.add_input('NodeSocketShader', 'Transform') + self.add_input('ArmDynamicSocket', 'Transform') - self.add_output('NodeSocketVector', 'Location') - self.add_output('NodeSocketVector', 'Rotation') - self.add_output('NodeSocketVector', 'Scale') + self.add_output('ArmVectorSocket', 'Location') + self.add_output('ArmVectorSocket', 'Rotation') + self.add_output('ArmVectorSocket', 'Scale') diff --git a/blender/arm/logicnode/transform/LN_set_object_location.py b/blender/arm/logicnode/transform/LN_set_object_location.py index 48afdffb..36a665b5 100644 --- a/blender/arm/logicnode/transform/LN_set_object_location.py +++ b/blender/arm/logicnode/transform/LN_set_object_location.py @@ -11,6 +11,6 @@ class SetLocationNode(ArmLogicTreeNode): super(SetLocationNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketVector', 'Location') + self.add_input('ArmVectorSocket', 'Location') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/transform/LN_set_object_rotation.py b/blender/arm/logicnode/transform/LN_set_object_rotation.py index b9c38a45..ac563ff5 100644 --- a/blender/arm/logicnode/transform/LN_set_object_rotation.py +++ b/blender/arm/logicnode/transform/LN_set_object_rotation.py @@ -11,8 +11,8 @@ class SetRotationNode(ArmLogicTreeNode): super(SetRotationNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketVector', 'Euler Angles / Vector XYZ') - self.add_input('NodeSocketFloat', 'Angle / W') + self.add_input('ArmVectorSocket', 'Euler Angles / Vector XYZ') + self.add_input('ArmFloatSocket', 'Angle / W') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/transform/LN_set_object_scale.py b/blender/arm/logicnode/transform/LN_set_object_scale.py index e9580801..d797cba3 100644 --- a/blender/arm/logicnode/transform/LN_set_object_scale.py +++ b/blender/arm/logicnode/transform/LN_set_object_scale.py @@ -11,6 +11,6 @@ class SetScaleNode(ArmLogicTreeNode): super(SetScaleNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketVector', 'Scale', default_value=[1.0, 1.0, 1.0]) + self.add_input('ArmVectorSocket', 'Scale', default_value=[1.0, 1.0, 1.0]) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/transform/LN_set_object_transform.py b/blender/arm/logicnode/transform/LN_set_object_transform.py index 8d965d3e..99b08ac7 100644 --- a/blender/arm/logicnode/transform/LN_set_object_transform.py +++ b/blender/arm/logicnode/transform/LN_set_object_transform.py @@ -10,6 +10,6 @@ class SetTransformNode(ArmLogicTreeNode): super(SetTransformNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketShader', 'Transform') + self.add_input('ArmDynamicSocket', 'Transform') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/transform/LN_transform.py b/blender/arm/logicnode/transform/LN_transform.py index 002502a8..95c1fea4 100644 --- a/blender/arm/logicnode/transform/LN_transform.py +++ b/blender/arm/logicnode/transform/LN_transform.py @@ -8,8 +8,8 @@ class TransformNode(ArmLogicTreeNode): def init(self, context): super(TransformNode, self).init(context) - self.add_input('NodeSocketVector', 'Location') - self.add_input('NodeSocketVector', 'Rotation') - self.add_input('NodeSocketVector', 'Scale', default_value=[1.0, 1.0, 1.0]) + self.add_input('ArmVectorSocket', 'Location') + self.add_input('ArmVectorSocket', 'Rotation') + self.add_input('ArmVectorSocket', 'Scale', default_value=[1.0, 1.0, 1.0]) - self.add_output('NodeSocketShader', 'Transform') + self.add_output('ArmDynamicSocket', 'Transform') diff --git a/blender/arm/logicnode/transform/LN_transform_math.py b/blender/arm/logicnode/transform/LN_transform_math.py index 282a9439..70a7b5dc 100644 --- a/blender/arm/logicnode/transform/LN_transform_math.py +++ b/blender/arm/logicnode/transform/LN_transform_math.py @@ -8,7 +8,7 @@ class TransformMathNode(ArmLogicTreeNode): def init(self, context): super(TransformMathNode, self).init(context) - self.add_input('NodeSocketShader', 'Transform 1') - self.add_input('NodeSocketShader', 'Transform 2') + self.add_input('ArmDynamicSocket', 'Transform 1') + self.add_input('ArmDynamicSocket', 'Transform 2') - self.add_output('NodeSocketShader', 'Result') + self.add_output('ArmDynamicSocket', 'Result') diff --git a/blender/arm/logicnode/transform/LN_transform_to_vector.py b/blender/arm/logicnode/transform/LN_transform_to_vector.py index 9fbc6c3d..9b01681d 100644 --- a/blender/arm/logicnode/transform/LN_transform_to_vector.py +++ b/blender/arm/logicnode/transform/LN_transform_to_vector.py @@ -8,11 +8,11 @@ class VectorFromTransformNode(ArmLogicTreeNode): def init(self, context): super(VectorFromTransformNode, self).init(context) - self.add_input('NodeSocketShader', 'Transform') + self.add_input('ArmDynamicSocket', 'Transform') - self.add_output('NodeSocketVector', 'Vector') - self.add_output('NodeSocketVector', 'Quaternion XYZ') - self.add_output('NodeSocketFloat', 'Quaternion W') + self.add_output('ArmVectorSocket', 'Vector') + self.add_output('ArmVectorSocket', 'Quaternion XYZ') + self.add_output('ArmFloatSocket', 'Quaternion W') def on_property_update(self, context): """called by the EnumProperty, used to update the node socket labels""" diff --git a/blender/arm/logicnode/transform/LN_translate_object.py b/blender/arm/logicnode/transform/LN_translate_object.py index 048043be..9076e9ad 100644 --- a/blender/arm/logicnode/transform/LN_translate_object.py +++ b/blender/arm/logicnode/transform/LN_translate_object.py @@ -11,7 +11,7 @@ class TranslateObjectNode(ArmLogicTreeNode): super(TranslateObjectNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketVector', 'Vector') - self.add_input('NodeSocketBool', 'On Local Axis') + self.add_input('ArmVectorSocket', 'Vector') + self.add_input('ArmBoolSocket', 'On Local Axis') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/transform/LN_translate_on_local_axis.py b/blender/arm/logicnode/transform/LN_translate_on_local_axis.py index 41c1af5c..35456dad 100644 --- a/blender/arm/logicnode/transform/LN_translate_on_local_axis.py +++ b/blender/arm/logicnode/transform/LN_translate_on_local_axis.py @@ -12,8 +12,8 @@ class TranslateOnLocalAxisNode(ArmLogicTreeNode): super(TranslateOnLocalAxisNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketFloat', 'Speed') - self.add_input('NodeSocketInt', 'Forward/Up/Right') - self.add_input('NodeSocketBool', 'Inverse') + self.add_input('ArmFloatSocket', 'Speed') + self.add_input('ArmIntSocket', 'Forward/Up/Right') + self.add_input('ArmBoolSocket', 'Inverse') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/transform/LN_vector_to_object_orientation.py b/blender/arm/logicnode/transform/LN_vector_to_object_orientation.py index e1402b6a..2b064684 100644 --- a/blender/arm/logicnode/transform/LN_vector_to_object_orientation.py +++ b/blender/arm/logicnode/transform/LN_vector_to_object_orientation.py @@ -16,6 +16,6 @@ class VectorToObjectOrientationNode(ArmLogicTreeNode): def init(self, context): super(VectorToObjectOrientationNode, self).init(context) self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketVector', 'World') + self.add_input('ArmVectorSocket', 'World') - self.add_output('NodeSocketVector', 'Oriented') + self.add_output('ArmVectorSocket', 'Oriented') diff --git a/blender/arm/logicnode/transform/LN_world_vector_to_local_space.py b/blender/arm/logicnode/transform/LN_world_vector_to_local_space.py index 61497f81..0895659e 100644 --- a/blender/arm/logicnode/transform/LN_world_vector_to_local_space.py +++ b/blender/arm/logicnode/transform/LN_world_vector_to_local_space.py @@ -16,6 +16,6 @@ class WorldVectorToLocalSpaceNode(ArmLogicTreeNode): def init(self, context): super(WorldVectorToLocalSpaceNode, self).init(context) self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketVector', 'World') + self.add_input('ArmVectorSocket', 'World') - self.add_output('NodeSocketVector', 'Local') + self.add_output('ArmVectorSocket', 'Local') diff --git a/blender/arm/logicnode/variable/LN_boolean.py b/blender/arm/logicnode/variable/LN_boolean.py index 08d597d1..810cdfc9 100644 --- a/blender/arm/logicnode/variable/LN_boolean.py +++ b/blender/arm/logicnode/variable/LN_boolean.py @@ -9,6 +9,6 @@ class BooleanNode(ArmLogicTreeNode): def init(self, context): super(BooleanNode, self).init(context) - self.add_input('NodeSocketBool', 'Bool In') + self.add_input('ArmBoolSocket', 'Bool In') - self.add_output('NodeSocketBool', 'Bool Out', is_var=True) + self.add_output('ArmBoolSocket', 'Bool Out', is_var=True) diff --git a/blender/arm/logicnode/variable/LN_color.py b/blender/arm/logicnode/variable/LN_color.py index 1723f052..3f7f805e 100644 --- a/blender/arm/logicnode/variable/LN_color.py +++ b/blender/arm/logicnode/variable/LN_color.py @@ -8,6 +8,6 @@ class ColorNode(ArmLogicTreeNode): def init(self, context): super(ColorNode, self).init(context) - self.add_input('NodeSocketColor', 'Color In', default_value=[1.0, 1.0, 1.0, 1.0]) + self.add_input('ArmColorSocket', 'Color In', default_value=[1.0, 1.0, 1.0, 1.0]) - self.add_output('NodeSocketColor', 'Color Out', is_var=True) + self.add_output('ArmColorSocket', 'Color Out', is_var=True) diff --git a/blender/arm/logicnode/variable/LN_dynamic.py b/blender/arm/logicnode/variable/LN_dynamic.py index 495d23da..11e1e73e 100644 --- a/blender/arm/logicnode/variable/LN_dynamic.py +++ b/blender/arm/logicnode/variable/LN_dynamic.py @@ -8,4 +8,4 @@ class DynamicNode(ArmLogicTreeNode): def init(self, context): super(DynamicNode, self).init(context) - self.add_output('NodeSocketShader', 'Dynamic', is_var=True) + self.add_output('ArmDynamicSocket', 'Dynamic', is_var=True) diff --git a/blender/arm/logicnode/variable/LN_float.py b/blender/arm/logicnode/variable/LN_float.py index a5c0f290..b660e53f 100644 --- a/blender/arm/logicnode/variable/LN_float.py +++ b/blender/arm/logicnode/variable/LN_float.py @@ -11,5 +11,5 @@ class FloatNode(ArmLogicTreeNode): def init(self, context): super(FloatNode, self).init(context) - self.add_input('NodeSocketFloat', 'Float In') - self.add_output('NodeSocketFloat', 'Float Out', is_var=True) + self.add_input('ArmFloatSocket', 'Float In') + self.add_output('ArmFloatSocket', 'Float Out', is_var=True) diff --git a/blender/arm/logicnode/variable/LN_integer.py b/blender/arm/logicnode/variable/LN_integer.py index f4f022f4..fb16b2d5 100644 --- a/blender/arm/logicnode/variable/LN_integer.py +++ b/blender/arm/logicnode/variable/LN_integer.py @@ -8,5 +8,5 @@ class IntegerNode(ArmLogicTreeNode): def init(self, context): super(IntegerNode, self).init(context) - self.add_input('NodeSocketInt', 'Int In') - self.add_output('NodeSocketInt', 'Int Out', is_var=True) + self.add_input('ArmIntSocket', 'Int In') + self.add_output('ArmIntSocket', 'Int Out', is_var=True) diff --git a/blender/arm/logicnode/variable/LN_mask.py b/blender/arm/logicnode/variable/LN_mask.py index 002c2472..68ec954f 100644 --- a/blender/arm/logicnode/variable/LN_mask.py +++ b/blender/arm/logicnode/variable/LN_mask.py @@ -10,6 +10,6 @@ class MaskNode(ArmLogicTreeNode): super(MaskNode, self).init(context) for i in range(1, 21): label = 'Group {:02d}'.format(i) - self.inputs.new('NodeSocketBool', label) + self.inputs.new('ArmBoolSocket', label) - self.add_output('NodeSocketInt', 'Mask', is_var=True) + self.add_output('ArmIntSocket', 'Mask', is_var=True) diff --git a/blender/arm/logicnode/variable/LN_quaternion.py b/blender/arm/logicnode/variable/LN_quaternion.py index 8f91465d..15eb6ca2 100644 --- a/blender/arm/logicnode/variable/LN_quaternion.py +++ b/blender/arm/logicnode/variable/LN_quaternion.py @@ -9,11 +9,11 @@ class QuaternionNode(ArmLogicTreeNode): def init(self, context): super(QuaternionNode, self).init(context) - self.add_input('NodeSocketFloat', 'X') - self.add_input('NodeSocketFloat', 'Y') - self.add_input('NodeSocketFloat', 'Z') - self.add_input('NodeSocketFloat', 'W', default_value=1.0) + self.add_input('ArmFloatSocket', 'X') + self.add_input('ArmFloatSocket', 'Y') + self.add_input('ArmFloatSocket', 'Z') + self.add_input('ArmFloatSocket', 'W', default_value=1.0) - self.add_output('NodeSocketVector', 'Quaternion') - self.add_output('NodeSocketVector', 'XYZ') - self.add_output('NodeSocketFloat', 'W') + self.add_output('ArmVectorSocket', 'Quaternion') + self.add_output('ArmVectorSocket', 'XYZ') + self.add_output('ArmFloatSocket', 'W') diff --git a/blender/arm/logicnode/variable/LN_set_variable.py b/blender/arm/logicnode/variable/LN_set_variable.py index d9ed2985..ae729ee7 100644 --- a/blender/arm/logicnode/variable/LN_set_variable.py +++ b/blender/arm/logicnode/variable/LN_set_variable.py @@ -16,7 +16,7 @@ class SetVariableNode(ArmLogicTreeNode): def init(self, context): super(SetVariableNode, self).init(context) self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Variable', is_var=True) - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmDynamicSocket', 'Variable', is_var=True) + self.add_input('ArmDynamicSocket', 'Value') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/variable/LN_vector.py b/blender/arm/logicnode/variable/LN_vector.py index 630f2a9f..d93cd2c6 100644 --- a/blender/arm/logicnode/variable/LN_vector.py +++ b/blender/arm/logicnode/variable/LN_vector.py @@ -8,8 +8,8 @@ class VectorNode(ArmLogicTreeNode): def init(self, context): super(VectorNode, self).init(context) - self.add_input('NodeSocketFloat', 'X') - self.add_input('NodeSocketFloat', 'Y') - self.add_input('NodeSocketFloat', 'Z') + self.add_input('ArmFloatSocket', 'X') + self.add_input('ArmFloatSocket', 'Y') + self.add_input('ArmFloatSocket', 'Z') - self.add_output('NodeSocketVector', 'Vector', is_var=True) + self.add_output('ArmVectorSocket', 'Vector', is_var=True) diff --git a/blender/arm/node_utils.py b/blender/arm/node_utils.py index e501707f..2e601884 100755 --- a/blender/arm/node_utils.py +++ b/blender/arm/node_utils.py @@ -5,6 +5,7 @@ from bpy.types import NodeSocket, NodeInputs, NodeOutputs from nodeitems_utils import NodeItem import arm.log +import arm.logicnode.arm_sockets import arm.utils @@ -60,6 +61,30 @@ def get_socket_index(sockets: Union[NodeInputs, NodeOutputs], socket: NodeSocket return -1 +def get_socket_default(socket: NodeSocket) -> Any: + """Get the socket's default value, or `None` if it doesn't exist.""" + if isinstance(socket, arm.logicnode.arm_sockets.ArmCustomSocket): + if socket.arm_socket_type != 'NONE': + return socket.default_value_raw + + # Shader-type sockets don't have a default value + elif socket.type != 'SHADER': + return socket.default_value + + return None + + +def set_socket_default(socket: NodeSocket, value: Any): + """Set the socket's default value if it exists.""" + if isinstance(socket, arm.logicnode.arm_sockets.ArmCustomSocket): + if socket.arm_socket_type != 'NONE': + socket.default_value_raw = value + + # Shader-type sockets don't have a default value + elif socket.type != 'SHADER': + socket.default_value = value + + def get_export_tree_name(tree: bpy.types.NodeTree, do_warn=False) -> str: """Return the name of the given node tree that's used in the exported Haxe code. diff --git a/blender/arm/nodes_logic.py b/blender/arm/nodes_logic.py index 12f605fb..ff1c20ce 100755 --- a/blender/arm/nodes_logic.py +++ b/blender/arm/nodes_logic.py @@ -354,7 +354,7 @@ class ReplaceNodesOperator(bpy.types.Operator): bl_description = "Replace deprecated nodes" def execute(self, context): - arm.logicnode.replacement.replace_all() + arm.logicnode.replacement.replace_all(force_replacement=True) return {'FINISHED'} @classmethod diff --git a/blender/arm/props.py b/blender/arm/props.py index 99497c80..8c48ce9f 100755 --- a/blender/arm/props.py +++ b/blender/arm/props.py @@ -492,18 +492,25 @@ def init_properties_on_load(): def update_armory_world(): global arm_version wrd = bpy.data.worlds['Arm'] + # Outdated project - if bpy.data.filepath != '' and (wrd.arm_version != arm_version or wrd.arm_commit != arm_commit): # Call on project load only - # This allows for seamless migration from ealier versions of Armory - for rp in wrd.arm_rplist: # TODO: deprecated + file_version = tuple(map(int, wrd.arm_version.split('.'))) + sdk_version = tuple(map(int, arm_version.split('.'))) + if bpy.data.filepath != '' and (file_version < sdk_version or wrd.arm_commit != arm_commit): + # This allows for seamless migration from earlier 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 + if file_version < (2021, 8): + # There were breaking changes in SDK 2021.08, use a special + # update routine first before regularly replacing nodes + arm.logicnode.replacement.node_compat_sdk2108() + arm.logicnode.replacement.replace_all() - print('Project updated to sdk v' + arm_version + ' (' + arm_commit + ')') + print(f'Project updated to SDK v{arm_version}({arm_commit})') wrd.arm_version = arm_version wrd.arm_commit = arm_commit arm.make.clean() diff --git a/blender/arm/utils.py b/blender/arm/utils.py index 7b3af738..2003a905 100755 --- a/blender/arm/utils.py +++ b/blender/arm/utils.py @@ -948,6 +948,10 @@ def get_link_web_server(): def compare_version_blender_arm(): return not (bpy.app.version[0] != 2 or bpy.app.version[1] != 93) +def get_file_arm_version_tuple() -> tuple[int]: + wrd = bpy.data.worlds['Arm'] + return tuple(map(int, wrd.arm_version.split('.'))) + def type_name_to_type(name: str) -> bpy.types.bpy_struct: """Return the Blender type given by its name, if registered.""" return bpy.types.bpy_struct.bl_rna_get_subclass_py(name) From ee194a18069cadc522762eb8eef3baf29a7ce752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 10 Jul 2021 21:50:30 +0200 Subject: [PATCH 213/264] Live patch: support for socket default values --- Sources/armory/trait/internal/LivePatch.hx | 10 +++++++++ blender/arm/live_patch.py | 26 +++++++++++++++++++++- blender/arm/logicnode/arm_nodes.py | 2 +- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/Sources/armory/trait/internal/LivePatch.hx b/Sources/armory/trait/internal/LivePatch.hx index 8d8be7db..7a9733e5 100644 --- a/Sources/armory/trait/internal/LivePatch.hx +++ b/Sources/armory/trait/internal/LivePatch.hx @@ -55,5 +55,15 @@ class LivePatch extends iron.Trait { Reflect.setField(node, propName, value); } + + public static function patchUpdateNodeInputVal(treeName: String, nodeName: String, socketIndex: Int, value: Dynamic) { + var tree = LogicTree.nodeTrees[treeName]; + if (tree == null) return; + + var node = tree.nodes[nodeName]; + if (node == null) return; + + @:privateAccess node.inputs[socketIndex].set(value); + } #end } diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index c9a89c9e..cfd8ecd5 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -178,7 +178,7 @@ def send_event(event_id: str, opt_data: Any = None): js = f'LivePatch.patchCreateNodeLink("{tree_name}", "{from_node_name}", "{to_node_name}", "{from_index}", "{to_index}");' write_patch(js) - if event_id == 'ln_update_prop': + elif event_id == 'ln_update_prop': node: ArmLogicTreeNode prop_name: str node, prop_name = opt_data @@ -191,6 +191,30 @@ def send_event(event_id: str, opt_data: Any = None): js = f'LivePatch.patchUpdateNodeProp("{tree_name}", "{node_name}", "{prop_name}", {value});' write_patch(js) + elif event_id == 'ln_socket_val': + node: ArmLogicTreeNode + socket: bpy.types.NodeSocket + node, socket = opt_data + + socket_index = arm.node_utils.get_socket_index(node.inputs, socket) + + if socket_index != -1: + tree_name = arm.node_utils.get_export_tree_name(node.get_tree()) + node_name = arm.node_utils.get_export_node_name(node)[1:] + + value = arm.node_utils.haxe_format_socket_val(socket.get_default_value()) + inp_type = socket.arm_socket_type + + if inp_type in ('VECTOR', 'RGB'): + value = '{' + f'"x": {value[0]}, "y": {value[1]}, "z": {value[2]}' + '}' + elif inp_type == 'RGBA': + value = '{' + f'"x": {value[0]}, "y": {value[1]}, "z": {value[2]}, "w": {value[3]}' + '}' + elif inp_type == 'BOOLEAN': + value = str(value).lower() + + js = f'LivePatch.patchUpdateNodeInputVal("{tree_name}", "{node_name}", {socket_index}, {value});' + write_patch(js) + def on_operator(operator_id: str): """As long as bpy.msgbus doesn't listen to changes made by diff --git a/blender/arm/logicnode/arm_nodes.py b/blender/arm/logicnode/arm_nodes.py index 7c929f31..1f8f9681 100644 --- a/blender/arm/logicnode/arm_nodes.py +++ b/blender/arm/logicnode/arm_nodes.py @@ -62,7 +62,7 @@ class ArmLogicTreeNode(bpy.types.Node): arm.live_patch.send_event('ln_update_prop', (self, prop_name)) def on_socket_val_update(self, context: bpy.types.Context, socket: bpy.types.NodeSocket): - pass + arm.live_patch.send_event('ln_socket_val', (self, socket)) def insert_link(self, link: bpy.types.NodeLink): """Called on *both* nodes when a link between two nodes is created.""" From 823cc379b65bf5ca7d56139c49a354861469f78c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 10 Jul 2021 22:08:19 +0200 Subject: [PATCH 214/264] Live patch: ignore more operators & fix code style --- blender/arm/live_patch.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index cfd8ecd5..f6b97e23 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -224,17 +224,29 @@ def on_operator(operator_id: str): """ if not __running: return - # Don't re-export the scene for the following operators - if operator_id in ("VIEW3D_OT_select", "OUTLINER_OT_item_activate", "OBJECT_OT_editmode_toggle", "NODE_OT_select", "NODE_OT_translate_attach_remove_on_cancel"): + + if operator_id in IGNORE_OPERATORS: return - if operator_id == "TRANSFORM_OT_translate": - send_event("obj_location") - elif operator_id in ("TRANSFORM_OT_rotate", "TRANSFORM_OT_trackball"): - send_event("obj_rotation") - elif operator_id == "TRANSFORM_OT_resize": - send_event("obj_scale") + if operator_id == 'TRANSFORM_OT_translate': + send_event('obj_location') + elif operator_id in ('TRANSFORM_OT_rotate', 'TRANSFORM_OT_trackball'): + send_event('obj_rotation') + elif operator_id == 'TRANSFORM_OT_resize': + send_event('obj_scale') # Rebuild else: patch_export() + + +# Don't re-export the scene for the following operators +IGNORE_OPERATORS = ( + 'VIEW3D_OT_select', + 'VIEW3D_OT_select_box', + 'OUTLINER_OT_item_activate', + 'OBJECT_OT_editmode_toggle', + 'NODE_OT_select', + 'NODE_OT_translate_attach_remove_on_cancel', + 'NODE_OT_translate_attach' +) From b3162d8f6ef07dd32adbc860d72e26c8c3c0ae2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 10 Jul 2021 22:41:19 +0200 Subject: [PATCH 215/264] Fix visual artifacts caused by invalid gbuffer2 on OpenGL --- .../deferred_light/deferred_light.frag.glsl | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Shaders/deferred_light/deferred_light.frag.glsl b/Shaders/deferred_light/deferred_light.frag.glsl index fe81fad3..fbe01534 100644 --- a/Shaders/deferred_light/deferred_light.frag.glsl +++ b/Shaders/deferred_light/deferred_light.frag.glsl @@ -21,7 +21,9 @@ uniform sampler2D gbufferD; uniform sampler2D gbuffer0; uniform sampler2D gbuffer1; +#ifdef _gbuffer2 uniform sampler2D gbuffer2; +#endif #ifdef _VoxelAOvar uniform sampler3D voxels; @@ -206,7 +208,9 @@ void main() { vec3 v = normalize(eye - p); float dotNV = max(dot(n, v), 0.0); +#ifdef _gbuffer2 vec4 g2 = textureLod(gbuffer2, texCoord, 0.0); +#endif #ifdef _MicroShadowing occspec.x = mix(1.0, occspec.x, dotNV); // AO Fresnel @@ -221,14 +225,16 @@ void main() { vec3 envl = shIrradiance(n, shirr); - if (g2.b < 0.5) { - envl = envl; - } else { - envl = vec3(1.0); - } + #ifdef _gbuffer2 + if (g2.b < 0.5) { + envl = envl; + } else { + envl = vec3(1.0); + } + #endif #ifdef _EnvTex - envl /= PI; + envl /= PI; #endif #else vec3 envl = vec3(1.0); From cfa941eab4097e720a52883c6fa83a98289e682b Mon Sep 17 00:00:00 2001 From: tong Date: Sat, 17 Jul 2021 18:49:37 +0200 Subject: [PATCH 216/264] Fix fetch trait prop type --- blender/arm/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/utils.py b/blender/arm/utils.py index 7b3af738..f790036c 100755 --- a/blender/arm/utils.py +++ b/blender/arm/utils.py @@ -300,7 +300,7 @@ script_warnings: Dict[str, List[Tuple[str, str]]] = {} # Script name -> List of # See https://regex101.com/r/bbrCzN/8 RX_MODIFIERS = r'(?P(?:public\s+|private\s+|static\s+|inline\s+|final\s+)*)?' # Optional modifiers RX_IDENTIFIER = r'(?P[_$a-z]+[_a-z0-9]*)' # Variable name, follow Haxe rules -RX_TYPE = r'(?::\s+(?P[_a-z]+[\._a-z0-9]*))?' # Optional type annotation +RX_TYPE = r'(?:\s*:\s*(?P[_a-z]+[\._a-z0-9]*))?' # Optional type annotation RX_VALUE = r'(?:\s*=\s*(?P(?:\".*\")|(?:[^;]+)|))?' # Optional default value PROP_REGEX_RAW = fr'@prop\s+{RX_MODIFIERS}(?Pvar|final)\s+{RX_IDENTIFIER}{RX_TYPE}{RX_VALUE};' From bdee03873b99904b9612b1b4fe4f8d202566bd26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sun, 18 Jul 2021 22:28:32 +0200 Subject: [PATCH 217/264] Live patch: support for node duplication --- Sources/armory/trait/internal/LivePatch.hx | 55 +++++++++++++++++++++- blender/arm/live_patch.py | 29 ++++++++++-- blender/arm/logicnode/arm_nodes.py | 6 +++ blender/arm/make_logic.py | 15 +++--- blender/arm/node_utils.py | 25 ++++++++-- 5 files changed, 114 insertions(+), 16 deletions(-) diff --git a/Sources/armory/trait/internal/LivePatch.hx b/Sources/armory/trait/internal/LivePatch.hx index 7a9733e5..61ff0af8 100644 --- a/Sources/armory/trait/internal/LivePatch.hx +++ b/Sources/armory/trait/internal/LivePatch.hx @@ -1,6 +1,6 @@ package armory.trait.internal; -import armory.logicnode.LogicNode.LogicNodeInput; +import armory.logicnode.LogicNode; import armory.logicnode.LogicTree; @@ -65,5 +65,58 @@ class LivePatch extends iron.Trait { @:privateAccess node.inputs[socketIndex].set(value); } + + public static function patchNodeCopy(treeName: String, nodeName: String, newNodeName: String, copyProps: Array, inputDatas: Array>, outputDatas: Array>) { + var tree = LogicTree.nodeTrees[treeName]; + if (tree == null) return; + + var node = tree.nodes[nodeName]; + if (node == null) return; + + // No further constructor parameters required here, all variable nodes + // use optional further parameters and all values are set later in this + // function. + var newNode = Type.createInstance(Type.getClass(node), [tree]); + newNode.name = newNodeName; + tree.nodes[newNodeName] = newNode; + + for (propName in copyProps) { + Reflect.setField(newNode, propName, Reflect.field(node, propName)); + } + + var i = 0; + for (inputData in inputDatas) { + newNode.addInput(createSocketDefaultNode(newNode, inputData[0], inputData[1]), i++); + } + + for (outputData in outputDatas) { + newNode.addOutputs([createSocketDefaultNode(newNode, outputData[0], outputData[1])]); + } + } + + static inline function createSocketDefaultNode(node: LogicNode, inputType: String, value: Dynamic): LogicNode { + return switch (inputType) { + case "VECTOR": + new armory.logicnode.VectorNode(node.tree, value[0], value[1], value[2]); + case "RGBA": + new armory.logicnode.ColorNode(node.tree, value[0], value[1], value[2], value[3]); + case "RGB": + new armory.logicnode.ColorNode(node.tree, value[0], value[1], value[2]); + case "VALUE": + new armory.logicnode.FloatNode(node.tree, value); + case "INT": + new armory.logicnode.IntegerNode(node.tree, value); + case "BOOLEAN": + new armory.logicnode.BooleanNode(node.tree, value); + case "STRING": + new armory.logicnode.StringNode(node.tree, value); + case "NONE": + new armory.logicnode.NullNode(node.tree); + case "OBJECT": + new armory.logicnode.ObjectNode(node.tree, value); + default: + new armory.logicnode.DynamicNode(node.tree, value); + } + } #end } diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index f6b97e23..0affd3f2 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -202,19 +202,40 @@ def send_event(event_id: str, opt_data: Any = None): tree_name = arm.node_utils.get_export_tree_name(node.get_tree()) node_name = arm.node_utils.get_export_node_name(node)[1:] - value = arm.node_utils.haxe_format_socket_val(socket.get_default_value()) + value = socket.get_default_value() inp_type = socket.arm_socket_type if inp_type in ('VECTOR', 'RGB'): value = '{' + f'"x": {value[0]}, "y": {value[1]}, "z": {value[2]}' + '}' elif inp_type == 'RGBA': value = '{' + f'"x": {value[0]}, "y": {value[1]}, "z": {value[2]}, "w": {value[3]}' + '}' - elif inp_type == 'BOOLEAN': - value = str(value).lower() + else: + value = arm.node_utils.haxe_format_socket_val(value) js = f'LivePatch.patchUpdateNodeInputVal("{tree_name}", "{node_name}", {socket_index}, {value});' write_patch(js) + elif event_id == 'ln_copy': + newnode: ArmLogicTreeNode + node: ArmLogicTreeNode + newnode, node = opt_data + + # Use newnode to get the tree, node has no id_data at this moment + tree_name = arm.node_utils.get_export_tree_name(newnode.get_tree()) + + newnode_name = arm.node_utils.get_export_node_name(newnode)[1:] + node_name = arm.node_utils.get_export_node_name(node)[1:] + + props_list = '[' + ','.join(f'"{p}"' for p in arm.node_utils.get_haxe_property_names(node)) + ']' + + inp_data = [(inp.arm_socket_type, inp.get_default_value()) for inp in newnode.inputs] + inp_data = arm.node_utils.haxe_format_socket_val(inp_data) + out_data = [(out.arm_socket_type, out.get_default_value()) for out in newnode.outputs] + out_data = arm.node_utils.haxe_format_socket_val(out_data) + + js = f'LivePatch.patchNodeCopy("{tree_name}", "{node_name}", "{newnode_name}", {props_list}, {inp_data}, {out_data});' + write_patch(js) + def on_operator(operator_id: str): """As long as bpy.msgbus doesn't listen to changes made by @@ -246,6 +267,8 @@ IGNORE_OPERATORS = ( 'VIEW3D_OT_select_box', 'OUTLINER_OT_item_activate', 'OBJECT_OT_editmode_toggle', + 'NODE_OT_duplicate_move', + 'NODE_OT_link', 'NODE_OT_select', 'NODE_OT_translate_attach_remove_on_cancel', 'NODE_OT_translate_attach' diff --git a/blender/arm/logicnode/arm_nodes.py b/blender/arm/logicnode/arm_nodes.py index 1f8f9681..6be13f4f 100644 --- a/blender/arm/logicnode/arm_nodes.py +++ b/blender/arm/logicnode/arm_nodes.py @@ -54,6 +54,12 @@ class ArmLogicTreeNode(bpy.types.Node): def get_tree(self): return self.id_data + def copy(self, node): + """Called if the node was copied. `self` holds the copied node, + `node` the original one. + """ + arm.live_patch.send_event('ln_copy', (self, node)) + def on_prop_update(self, context: bpy.types.Context, prop_name: str): """Called if a property created with a function from the arm_props module is changed. If the property has a custom update diff --git a/blender/arm/make_logic.py b/blender/arm/make_logic.py index eef667fe..cc6b9a4d 100755 --- a/blender/arm/make_logic.py +++ b/blender/arm/make_logic.py @@ -267,28 +267,27 @@ def build_default_node(inp: bpy.types.NodeSocket): if is_custom_socket: # ArmCustomSockets need to implement get_default_value() - default_value = arm.node_utils.haxe_format_socket_val(inp.get_default_value()) + default_value = inp.get_default_value() inp_type = inp.arm_socket_type # any custom socket's `type` is "VALUE". might as well have valuable type information for custom nodes as well. else: if hasattr(inp, 'default_value'): default_value = inp.default_value else: default_value = None - default_value = arm.node_utils.haxe_format_socket_val(default_value) inp_type = inp.type + default_value = arm.node_utils.haxe_format_socket_val(default_value, array_outer_brackets=False) + if inp_type == 'VECTOR': - return f'new armory.logicnode.VectorNode(this, {default_value[0]}, {default_value[1]}, {default_value[2]})' - elif inp_type == 'RGBA': - return f'new armory.logicnode.ColorNode(this, {default_value[0]}, {default_value[1]}, {default_value[2]}, {default_value[3]})' - elif inp_type == 'RGB': - return f'new armory.logicnode.ColorNode(this, {default_value[0]}, {default_value[1]}, {default_value[2]})' + return f'new armory.logicnode.VectorNode(this, {default_value})' + elif inp_type in ('RGB', 'RGBA'): + return f'new armory.logicnode.ColorNode(this, {default_value})' elif inp_type == 'VALUE': return f'new armory.logicnode.FloatNode(this, {default_value})' elif inp_type == 'INT': return f'new armory.logicnode.IntegerNode(this, {default_value})' elif inp_type == 'BOOLEAN': - return f'new armory.logicnode.BooleanNode(this, {str(default_value).lower()})' + return f'new armory.logicnode.BooleanNode(this, {default_value})' elif inp_type == 'STRING': return f'new armory.logicnode.StringNode(this, {default_value})' elif inp_type == 'NONE': diff --git a/blender/arm/node_utils.py b/blender/arm/node_utils.py index 2e601884..2a9621fe 100755 --- a/blender/arm/node_utils.py +++ b/blender/arm/node_utils.py @@ -1,6 +1,8 @@ +import collections from typing import Any, Generator, Type, Union import bpy +import mathutils from bpy.types import NodeSocket, NodeInputs, NodeOutputs from nodeitems_utils import NodeItem @@ -122,16 +124,31 @@ def get_haxe_property_names(node: bpy.types.Node) -> Generator[str, None, None]: yield prop_name -def haxe_format_socket_val(socket_val: Any) -> str: - """Formats a socket value to be valid Haxe syntax.""" - if isinstance(socket_val, str): +def haxe_format_socket_val(socket_val: Any, array_outer_brackets=True) -> str: + """Formats a socket value to be valid Haxe syntax. + + If `array_outer_brackets` is false, no square brackets are put + around array values. + + Make sure that elements of sequence types are not yet in Haxe + syntax, otherwise they are strings and get additional quotes! + """ + if isinstance(socket_val, bool): + socket_val = str(socket_val).lower() + + elif isinstance(socket_val, str): socket_val = '"{:s}"'.format(socket_val.replace('"', '\\"')) + elif isinstance(socket_val, (collections.Sequence, bpy.types.bpy_prop_array, mathutils.Color, mathutils.Euler, mathutils.Vector)): + socket_val = ','.join(haxe_format_socket_val(v, array_outer_brackets=True) for v in socket_val) + if array_outer_brackets: + socket_val = f'[{socket_val}]' + elif socket_val is None: # Don't write 'None' into the Haxe code socket_val = 'null' - return socket_val + return str(socket_val) def haxe_format_prop(node: bpy.types.Node, prop_name: str) -> str: From c65764be9902884e9483c266599f4f47efe73968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Tue, 20 Jul 2021 14:24:00 +0200 Subject: [PATCH 218/264] Cleanup LivePatch.hx --- Sources/armory/trait/internal/LivePatch.hx | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Sources/armory/trait/internal/LivePatch.hx b/Sources/armory/trait/internal/LivePatch.hx index 61ff0af8..d51c43a1 100644 --- a/Sources/armory/trait/internal/LivePatch.hx +++ b/Sources/armory/trait/internal/LivePatch.hx @@ -86,36 +86,36 @@ class LivePatch extends iron.Trait { var i = 0; for (inputData in inputDatas) { - newNode.addInput(createSocketDefaultNode(newNode, inputData[0], inputData[1]), i++); + newNode.addInput(createSocketDefaultNode(newNode.tree, inputData[0], inputData[1]), i++); } for (outputData in outputDatas) { - newNode.addOutputs([createSocketDefaultNode(newNode, outputData[0], outputData[1])]); + newNode.addOutputs([createSocketDefaultNode(newNode.tree, outputData[0], outputData[1])]); } } - static inline function createSocketDefaultNode(node: LogicNode, inputType: String, value: Dynamic): LogicNode { + static inline function createSocketDefaultNode(tree: LogicTree, inputType: String, value: Dynamic): LogicNode { return switch (inputType) { case "VECTOR": - new armory.logicnode.VectorNode(node.tree, value[0], value[1], value[2]); + new armory.logicnode.VectorNode(tree, value[0], value[1], value[2]); case "RGBA": - new armory.logicnode.ColorNode(node.tree, value[0], value[1], value[2], value[3]); + new armory.logicnode.ColorNode(tree, value[0], value[1], value[2], value[3]); case "RGB": - new armory.logicnode.ColorNode(node.tree, value[0], value[1], value[2]); + new armory.logicnode.ColorNode(tree, value[0], value[1], value[2]); case "VALUE": - new armory.logicnode.FloatNode(node.tree, value); + new armory.logicnode.FloatNode(tree, value); case "INT": - new armory.logicnode.IntegerNode(node.tree, value); + new armory.logicnode.IntegerNode(tree, value); case "BOOLEAN": - new armory.logicnode.BooleanNode(node.tree, value); + new armory.logicnode.BooleanNode(tree, value); case "STRING": - new armory.logicnode.StringNode(node.tree, value); + new armory.logicnode.StringNode(tree, value); case "NONE": - new armory.logicnode.NullNode(node.tree); + new armory.logicnode.NullNode(tree); case "OBJECT": - new armory.logicnode.ObjectNode(node.tree, value); + new armory.logicnode.ObjectNode(tree, value); default: - new armory.logicnode.DynamicNode(node.tree, value); + new armory.logicnode.DynamicNode(tree, value); } } #end From e930da7388d2da2277d53b03a62de2f97c2ec5b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Tue, 20 Jul 2021 14:25:35 +0200 Subject: [PATCH 219/264] Live patch: support for node deletion --- Sources/armory/trait/internal/LivePatch.hx | 39 ++++++++++++++++++++++ blender/arm/live_patch.py | 12 +++++++ blender/arm/logicnode/arm_nodes.py | 4 +++ 3 files changed, 55 insertions(+) diff --git a/Sources/armory/trait/internal/LivePatch.hx b/Sources/armory/trait/internal/LivePatch.hx index d51c43a1..190a85ff 100644 --- a/Sources/armory/trait/internal/LivePatch.hx +++ b/Sources/armory/trait/internal/LivePatch.hx @@ -66,6 +66,45 @@ class LivePatch extends iron.Trait { @:privateAccess node.inputs[socketIndex].set(value); } + public static function patchNodeDelete(treeName: String, nodeName: String, outputDatas: Array>) { + var tree = LogicTree.nodeTrees[treeName]; + if (tree == null) return; + + var node = tree.nodes[nodeName]; + if (node == null) return; + + // Remove this node from the outputs of connected nodes + for (input in node.inputs) { + var inNodeOutputs = input.node.outputs; + + // Default nodes don't have outputs when exported from Blender + if (input.from < inNodeOutputs.length) { + for (outNode in inNodeOutputs[input.from]) { + if (outNode == node) { + inNodeOutputs[input.from].remove(outNode); + } + } + } + + } + + // Replace connected inputs of other nodes with default nodes + for (outputNodes in node.outputs) { + for (outNode in outputNodes) { + for (outNodeInput in outNode.inputs) { + if (outNodeInput.node == node) { + var outputIndex = outNodeInput.from; + var socketType = outputDatas[outputIndex][0]; + var socketValue = outputDatas[outputIndex][1]; + outNodeInput.node = createSocketDefaultNode(node.tree, socketType, socketValue); + } + } + } + } + + tree.nodes.remove(nodeName); + } + public static function patchNodeCopy(treeName: String, nodeName: String, newNodeName: String, copyProps: Array, inputDatas: Array>, outputDatas: Array>) { var tree = LogicTree.nodeTrees[treeName]; if (tree == null) return; diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index 0affd3f2..1130e5b5 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -215,6 +215,18 @@ def send_event(event_id: str, opt_data: Any = None): js = f'LivePatch.patchUpdateNodeInputVal("{tree_name}", "{node_name}", {socket_index}, {value});' write_patch(js) + elif event_id == 'ln_delete': + node: ArmLogicTreeNode = opt_data + + tree_name = arm.node_utils.get_export_tree_name(node.get_tree()) + node_name = arm.node_utils.get_export_node_name(node)[1:] + + out_data = [(out.arm_socket_type, out.get_default_value()) for out in node.outputs] + out_data = arm.node_utils.haxe_format_socket_val(out_data) + + js = f'LivePatch.patchNodeDelete("{tree_name}", "{node_name}", {out_data});' + write_patch(js) + elif event_id == 'ln_copy': newnode: ArmLogicTreeNode node: ArmLogicTreeNode diff --git a/blender/arm/logicnode/arm_nodes.py b/blender/arm/logicnode/arm_nodes.py index 6be13f4f..50db2c43 100644 --- a/blender/arm/logicnode/arm_nodes.py +++ b/blender/arm/logicnode/arm_nodes.py @@ -54,6 +54,10 @@ class ArmLogicTreeNode(bpy.types.Node): def get_tree(self): return self.id_data + def free(self): + """Called before the node is deleted.""" + arm.live_patch.send_event('ln_delete', self) + def copy(self, node): """Called if the node was copied. `self` holds the copied node, `node` the original one. From 3ed915b65418147141548a2a61b44d1caf10b909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Tue, 20 Jul 2021 14:28:42 +0200 Subject: [PATCH 220/264] Cleanup LivePatch.hx --- Sources/armory/trait/internal/LivePatch.hx | 34 ++++++++-------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/Sources/armory/trait/internal/LivePatch.hx b/Sources/armory/trait/internal/LivePatch.hx index 190a85ff..69c33452 100644 --- a/Sources/armory/trait/internal/LivePatch.hx +++ b/Sources/armory/trait/internal/LivePatch.hx @@ -133,28 +133,18 @@ class LivePatch extends iron.Trait { } } - static inline function createSocketDefaultNode(tree: LogicTree, inputType: String, value: Dynamic): LogicNode { - return switch (inputType) { - case "VECTOR": - new armory.logicnode.VectorNode(tree, value[0], value[1], value[2]); - case "RGBA": - new armory.logicnode.ColorNode(tree, value[0], value[1], value[2], value[3]); - case "RGB": - new armory.logicnode.ColorNode(tree, value[0], value[1], value[2]); - case "VALUE": - new armory.logicnode.FloatNode(tree, value); - case "INT": - new armory.logicnode.IntegerNode(tree, value); - case "BOOLEAN": - new armory.logicnode.BooleanNode(tree, value); - case "STRING": - new armory.logicnode.StringNode(tree, value); - case "NONE": - new armory.logicnode.NullNode(tree); - case "OBJECT": - new armory.logicnode.ObjectNode(tree, value); - default: - new armory.logicnode.DynamicNode(tree, value); + static inline function createSocketDefaultNode(tree: LogicTree, socketType: String, value: Dynamic): LogicNode { + return switch (socketType) { + case "VECTOR": new armory.logicnode.VectorNode(tree, value[0], value[1], value[2]); + case "RGBA": new armory.logicnode.ColorNode(tree, value[0], value[1], value[2], value[3]); + case "RGB": new armory.logicnode.ColorNode(tree, value[0], value[1], value[2]); + case "VALUE": new armory.logicnode.FloatNode(tree, value); + case "INT": new armory.logicnode.IntegerNode(tree, value); + case "BOOLEAN": new armory.logicnode.BooleanNode(tree, value); + case "STRING": new armory.logicnode.StringNode(tree, value); + case "NONE": new armory.logicnode.NullNode(tree); + case "OBJECT": new armory.logicnode.ObjectNode(tree, value); + default: new armory.logicnode.DynamicNode(tree, value); } } #end From 1edc7a9469617e1844fceaaace22a0446b080afc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Tue, 20 Jul 2021 20:53:22 +0200 Subject: [PATCH 221/264] Live patch: support for adding nodes --- Sources/armory/logicnode/LogicNode.hx | 1 + Sources/armory/trait/internal/LivePatch.hx | 28 ++++++++++++++++++- blender/arm/live_patch.py | 21 +++++++++++++- blender/arm/logicnode/animation/LN_action.py | 3 +- .../logicnode/animation/LN_blend_action.py | 3 +- blender/arm/logicnode/animation/LN_bone_fk.py | 3 +- blender/arm/logicnode/animation/LN_bone_ik.py | 3 +- .../animation/LN_get_action_state.py | 3 +- .../animation/LN_get_tilesheet_state.py | 3 +- .../animation/LN_on_action_marker.py | 3 +- .../animation/LN_play_action_from.py | 5 ++-- .../logicnode/animation/LN_play_tilesheet.py | 3 +- .../animation/LN_set_action_paused.py | 3 +- .../animation/LN_set_action_speed.py | 3 +- .../logicnode/animation/LN_set_parent_bone.py | 3 +- .../animation/LN_set_particle_speed.py | 3 +- .../animation/LN_set_tilesheet_paused.py | 3 +- blender/arm/logicnode/arm_nodes.py | 8 ++++++ blender/arm/logicnode/array/LN_array.py | 3 +- blender/arm/logicnode/array/LN_array_add.py | 3 +- .../arm/logicnode/array/LN_array_boolean.py | 3 +- blender/arm/logicnode/array/LN_array_color.py | 3 +- .../arm/logicnode/array/LN_array_contains.py | 3 +- blender/arm/logicnode/array/LN_array_float.py | 3 +- blender/arm/logicnode/array/LN_array_get.py | 3 +- .../arm/logicnode/array/LN_array_integer.py | 3 +- .../arm/logicnode/array/LN_array_length.py | 3 +- .../arm/logicnode/array/LN_array_loop_node.py | 3 +- .../arm/logicnode/array/LN_array_object.py | 3 +- blender/arm/logicnode/array/LN_array_pop.py | 3 +- .../array/LN_array_remove_by_index.py | 3 +- .../array/LN_array_remove_by_value.py | 3 +- blender/arm/logicnode/array/LN_array_set.py | 3 +- blender/arm/logicnode/array/LN_array_shift.py | 3 +- blender/arm/logicnode/array/LN_array_slice.py | 3 +- .../arm/logicnode/array/LN_array_splice.py | 3 +- .../arm/logicnode/array/LN_array_string.py | 3 +- .../arm/logicnode/array/LN_array_vector.py | 3 +- .../logicnode/camera/LN_get_camera_active.py | 3 +- .../arm/logicnode/camera/LN_get_camera_fov.py | 3 +- .../logicnode/camera/LN_set_camera_active.py | 3 +- .../arm/logicnode/camera/LN_set_camera_fov.py | 3 +- .../canvas/LN_get_canvas_checkbox.py | 3 +- .../canvas/LN_get_canvas_input_text.py | 3 +- .../canvas/LN_get_canvas_location.py | 3 +- .../canvas/LN_get_canvas_position.py | 3 +- .../canvas/LN_get_canvas_progress_bar.py | 3 +- .../canvas/LN_get_canvas_rotation.py | 3 +- .../logicnode/canvas/LN_get_canvas_scale.py | 3 +- .../logicnode/canvas/LN_get_canvas_slider.py | 3 +- .../logicnode/canvas/LN_get_canvas_visible.py | 3 +- .../logicnode/canvas/LN_on_canvas_element.py | 3 +- .../logicnode/canvas/LN_set_canvas_asset.py | 3 +- .../canvas/LN_set_canvas_checkbox.py | 3 +- .../canvas/LN_set_canvas_location.py | 3 +- .../canvas/LN_set_canvas_progress_bar.py | 3 +- .../canvas/LN_set_canvas_rotation.py | 3 +- .../logicnode/canvas/LN_set_canvas_scale.py | 3 +- .../logicnode/canvas/LN_set_canvas_slider.py | 3 +- .../logicnode/canvas/LN_set_canvas_text.py | 3 +- .../canvas/LN_set_canvas_text_color.py | 3 +- .../logicnode/canvas/LN_set_canvas_visible.py | 3 +- .../logicnode/deprecated/LN_get_mouse_lock.py | 3 +- .../deprecated/LN_get_mouse_visible.py | 3 +- .../logicnode/deprecated/LN_mouse_coords.py | 3 +- .../arm/logicnode/deprecated/LN_on_gamepad.py | 3 +- .../logicnode/deprecated/LN_on_keyboard.py | 3 +- .../arm/logicnode/deprecated/LN_on_mouse.py | 3 +- .../arm/logicnode/deprecated/LN_on_surface.py | 3 +- .../deprecated/LN_on_virtual_button.py | 3 +- .../logicnode/deprecated/LN_pause_action.py | 3 +- .../deprecated/LN_pause_tilesheet.py | 3 +- .../logicnode/deprecated/LN_pause_trait.py | 3 +- .../logicnode/deprecated/LN_play_action.py | 3 +- .../logicnode/deprecated/LN_resume_action.py | 3 +- .../deprecated/LN_resume_tilesheet.py | 3 +- .../logicnode/deprecated/LN_resume_trait.py | 3 +- .../LN_rotate_object_around_axis.py | 3 +- .../logicnode/deprecated/LN_scale_object.py | 3 +- .../logicnode/deprecated/LN_set_mouse_lock.py | 3 +- .../deprecated/LN_set_mouse_visible.py | 3 +- .../deprecated/LN_set_object_material.py | 3 +- .../logicnode/deprecated/LN_surface_coords.py | 3 +- .../event/LN_on_application_state.py | 3 +- blender/arm/logicnode/event/LN_on_event.py | 3 +- blender/arm/logicnode/event/LN_on_init.py | 3 +- blender/arm/logicnode/event/LN_on_timer.py | 3 +- blender/arm/logicnode/event/LN_on_update.py | 3 +- .../event/LN_send_event_to_object.py | 3 +- .../logicnode/event/LN_send_global_event.py | 3 +- blender/arm/logicnode/input/LN_gamepad.py | 3 +- .../arm/logicnode/input/LN_gamepad_coords.py | 3 +- .../logicnode/input/LN_get_cursor_location.py | 3 +- .../logicnode/input/LN_get_cursor_state.py | 3 +- .../logicnode/input/LN_get_mouse_movement.py | 3 +- .../logicnode/input/LN_get_touch_location.py | 3 +- .../logicnode/input/LN_get_touch_movement.py | 3 +- blender/arm/logicnode/input/LN_keyboard.py | 3 +- blender/arm/logicnode/input/LN_mouse.py | 3 +- blender/arm/logicnode/input/LN_on_swipe.py | 3 +- .../arm/logicnode/input/LN_on_tap_screen.py | 3 +- .../arm/logicnode/input/LN_sensor_coords.py | 3 +- .../logicnode/input/LN_set_cursor_state.py | 3 +- blender/arm/logicnode/input/LN_touch.py | 3 +- .../arm/logicnode/input/LN_virtual_button.py | 3 +- .../arm/logicnode/light/LN_set_light_color.py | 3 +- .../logicnode/light/LN_set_light_strength.py | 3 +- .../logicnode/logic/LN_alternate_output.py | 3 +- blender/arm/logicnode/logic/LN_branch.py | 3 +- .../arm/logicnode/logic/LN_call_function.py | 3 +- blender/arm/logicnode/logic/LN_function.py | 3 +- .../arm/logicnode/logic/LN_function_output.py | 3 +- blender/arm/logicnode/logic/LN_gate.py | 3 +- .../arm/logicnode/logic/LN_invert_boolean.py | 3 +- .../arm/logicnode/logic/LN_invert_output.py | 3 +- blender/arm/logicnode/logic/LN_is_false.py | 3 +- blender/arm/logicnode/logic/LN_is_not_null.py | 3 +- blender/arm/logicnode/logic/LN_is_null.py | 3 +- blender/arm/logicnode/logic/LN_is_true.py | 3 +- blender/arm/logicnode/logic/LN_loop.py | 3 +- blender/arm/logicnode/logic/LN_loop_break.py | 3 +- blender/arm/logicnode/logic/LN_merge.py | 3 +- blender/arm/logicnode/logic/LN_null.py | 3 +- .../arm/logicnode/logic/LN_output_sequence.py | 3 +- .../logicnode/logic/LN_output_to_boolean.py | 3 +- .../arm/logicnode/logic/LN_switch_output.py | 3 +- .../arm/logicnode/logic/LN_value_changed.py | 3 +- blender/arm/logicnode/logic/LN_while_true.py | 3 +- .../material/LN_get_object_material.py | 3 +- blender/arm/logicnode/material/LN_material.py | 3 +- .../material/LN_set_material_image_param.py | 3 +- .../material/LN_set_material_rgb_param.py | 3 +- .../material/LN_set_material_value_param.py | 3 +- .../material/LN_set_object_material_slot.py | 3 +- blender/arm/logicnode/math/LN_clamp.py | 3 +- blender/arm/logicnode/math/LN_compare.py | 3 +- blender/arm/logicnode/math/LN_deg_to_rad.py | 3 +- blender/arm/logicnode/math/LN_map_range.py | 3 +- blender/arm/logicnode/math/LN_math.py | 3 +- .../arm/logicnode/math/LN_math_expression.py | 3 +- blender/arm/logicnode/math/LN_matrix_math.py | 3 +- blender/arm/logicnode/math/LN_mix.py | 3 +- blender/arm/logicnode/math/LN_mix_vector.py | 3 +- .../arm/logicnode/math/LN_quaternion_math.py | 3 +- blender/arm/logicnode/math/LN_rad_to_deg.py | 3 +- .../math/LN_screen_to_world_space.py | 3 +- .../logicnode/math/LN_separate_quaternion.py | 3 +- blender/arm/logicnode/math/LN_separate_rgb.py | 3 +- blender/arm/logicnode/math/LN_separate_xyz.py | 3 +- blender/arm/logicnode/math/LN_vector_clamp.py | 3 +- blender/arm/logicnode/math/LN_vector_math.py | 3 +- .../math/LN_world_to_screen_space.py | 3 +- .../miscellaneous/LN_boolean_to_vector.py | 3 +- .../logicnode/miscellaneous/LN_call_group.py | 3 +- .../miscellaneous/LN_default_if_null.py | 3 +- .../miscellaneous/LN_get_application_time.py | 3 +- .../LN_get_debug_console_settings.py | 3 +- .../LN_get_display_resolution.py | 3 +- .../arm/logicnode/miscellaneous/LN_get_fps.py | 3 +- .../miscellaneous/LN_get_window_resolution.py | 3 +- .../logicnode/miscellaneous/LN_group_nodes.py | 3 +- .../LN_set_debug_console_settings.py | 3 +- .../miscellaneous/LN_set_time_scale.py | 3 +- .../arm/logicnode/miscellaneous/LN_sleep.py | 3 +- .../arm/logicnode/miscellaneous/LN_timer.py | 3 +- .../logicnode/native/LN_call_haxe_static.py | 3 +- .../native/LN_detect_mobile_browser.py | 3 +- blender/arm/logicnode/native/LN_expression.py | 3 +- .../logicnode/native/LN_get_haxe_property.py | 3 +- .../native/LN_get_system_language.py | 3 +- .../logicnode/native/LN_get_system_name.py | 3 +- blender/arm/logicnode/native/LN_loadUrl.py | 3 +- blender/arm/logicnode/native/LN_print.py | 3 +- blender/arm/logicnode/native/LN_read_file.py | 3 +- blender/arm/logicnode/native/LN_read_json.py | 3 +- .../arm/logicnode/native/LN_read_storage.py | 3 +- blender/arm/logicnode/native/LN_script.py | 3 +- .../logicnode/native/LN_set_haxe_property.py | 3 +- .../arm/logicnode/native/LN_set_vibrate.py | 3 +- blender/arm/logicnode/native/LN_shutdown.py | 3 +- blender/arm/logicnode/native/LN_write_file.py | 3 +- blender/arm/logicnode/native/LN_write_json.py | 3 +- .../arm/logicnode/native/LN_write_storage.py | 3 +- .../logicnode/navmesh/LN_go_to_location.py | 3 +- .../navmesh/LN_navigable_location.py | 3 +- .../navmesh/LN_pick_navmesh_location.py | 3 +- .../arm/logicnode/navmesh/LN_stop_agent.py | 3 +- .../arm/logicnode/object/LN_get_distance.py | 3 +- .../logicnode/object/LN_get_object_by_name.py | 5 ++-- .../logicnode/object/LN_get_object_child.py | 3 +- .../object/LN_get_object_children.py | 3 +- .../logicnode/object/LN_get_object_mesh.py | 3 +- .../logicnode/object/LN_get_object_name.py | 3 +- .../object/LN_get_object_offscreen.py | 3 +- .../logicnode/object/LN_get_object_parent.py | 3 +- .../object/LN_get_object_property.py | 3 +- .../logicnode/object/LN_get_object_visible.py | 3 +- blender/arm/logicnode/object/LN_mesh.py | 3 +- blender/arm/logicnode/object/LN_object.py | 3 +- .../arm/logicnode/object/LN_remove_object.py | 3 +- .../object/LN_remove_object_parent.py | 3 +- .../arm/logicnode/object/LN_self_object.py | 3 +- .../logicnode/object/LN_set_object_mesh.py | 3 +- .../logicnode/object/LN_set_object_name.py | 3 +- .../logicnode/object/LN_set_object_parent.py | 3 +- .../object/LN_set_object_property.py | 3 +- .../logicnode/object/LN_set_object_visible.py | 3 +- .../arm/logicnode/object/LN_spawn_object.py | 5 ++-- .../object/LN_spawn_object_by_name.py | 3 +- .../logicnode/physics/LN_Add_rigid_body.py | 3 +- .../physics/LN_add_physics_constraint.py | 3 +- .../arm/logicnode/physics/LN_apply_force.py | 3 +- .../physics/LN_apply_force_at_location.py | 3 +- .../arm/logicnode/physics/LN_apply_impulse.py | 3 +- .../physics/LN_apply_impulse_at_location.py | 3 +- .../arm/logicnode/physics/LN_apply_torque.py | 3 +- .../physics/LN_apply_torque_impulse.py | 3 +- .../logicnode/physics/LN_get_rb_contacts.py | 3 +- .../physics/LN_get_rb_first_contact.py | 3 +- .../logicnode/physics/LN_get_rb_velocity.py | 3 +- .../logicnode/physics/LN_get_world_gravity.py | 3 +- .../arm/logicnode/physics/LN_has_contact.py | 3 +- .../logicnode/physics/LN_has_contact_array.py | 3 +- .../arm/logicnode/physics/LN_on_contact.py | 3 +- .../logicnode/physics/LN_on_contact_array.py | 3 +- .../logicnode/physics/LN_on_volume_trigger.py | 3 +- .../physics/LN_physics_constraint.py | 3 +- blender/arm/logicnode/physics/LN_pick_rb.py | 11 ++++---- blender/arm/logicnode/physics/LN_ray_cast.py | 3 +- blender/arm/logicnode/physics/LN_remove_rb.py | 3 +- .../physics/LN_set_rb_activation_state.py | 3 +- .../logicnode/physics/LN_set_rb_friction.py | 3 +- .../physics/LN_set_rb_gravity_enabled.py | 3 +- .../logicnode/physics/LN_set_rb_velocity.py | 3 +- .../logicnode/physics/LN_set_world_gravity.py | 3 +- .../logicnode/physics/LN_volume_trigger.py | 3 +- .../LN_colorgrading_get_global_node.py | 3 +- .../LN_colorgrading_get_highlight_node.py | 3 +- .../LN_colorgrading_get_midtone_node.py | 3 +- .../LN_colorgrading_get_shadow_node.py | 3 +- .../LN_colorgrading_set_global_node.py | 3 +- .../LN_colorgrading_set_highlight_node.py | 3 +- .../LN_colorgrading_set_midtone_node.py | 3 +- .../LN_colorgrading_set_shadow_node.py | 3 +- .../postprocess/LN_get_bloom_settings.py | 3 +- .../postprocess/LN_get_ca_settings.py | 3 +- .../postprocess/LN_get_camera_post_process.py | 3 +- .../LN_get_lenstexture_settings.py | 3 +- .../postprocess/LN_get_ssao_settings.py | 3 +- .../postprocess/LN_get_ssr_settings.py | 3 +- .../postprocess/LN_lenstexture_set.py | 3 +- .../postprocess/LN_set_bloom_settings.py | 3 +- .../postprocess/LN_set_ca_settings.py | 3 +- .../postprocess/LN_set_camera_post_process.py | 3 +- .../postprocess/LN_set_ssao_settings.py | 3 +- .../postprocess/LN_set_ssr_settings.py | 3 +- .../arm/logicnode/random/LN_random_boolean.py | 3 +- .../arm/logicnode/random/LN_random_choice.py | 3 +- .../arm/logicnode/random/LN_random_color.py | 3 +- .../arm/logicnode/random/LN_random_float.py | 3 +- .../arm/logicnode/random/LN_random_integer.py | 3 +- .../arm/logicnode/random/LN_random_output.py | 3 +- .../arm/logicnode/random/LN_random_vector.py | 3 +- .../renderpath/LN_set_msaa_quality.py | 3 +- .../renderpath/LN_set_post_process_quality.py | 3 +- .../renderpath/LN_set_shader_uniform.py | 3 +- .../renderpath/LN_set_shadows_quality.py | 3 +- .../renderpath/LN_set_ssaa_quality.py | 3 +- blender/arm/logicnode/scene/LN_collection.py | 3 +- .../logicnode/scene/LN_create_collection.py | 3 +- .../arm/logicnode/scene/LN_get_collection.py | 3 +- .../logicnode/scene/LN_get_scene_active.py | 3 +- .../arm/logicnode/scene/LN_get_scene_root.py | 3 +- .../arm/logicnode/scene/LN_global_object.py | 3 +- .../logicnode/scene/LN_remove_collection.py | 3 +- .../logicnode/scene/LN_remove_scene_active.py | 3 +- blender/arm/logicnode/scene/LN_scene.py | 3 +- .../logicnode/scene/LN_set_scene_active.py | 3 +- .../logicnode/scene/LN_spawn_collection.py | 3 +- blender/arm/logicnode/scene/LN_spawn_scene.py | 3 +- .../arm/logicnode/sound/LN_pause_speaker.py | 3 +- blender/arm/logicnode/sound/LN_play_sound.py | 3 +- .../arm/logicnode/sound/LN_play_speaker.py | 3 +- .../arm/logicnode/sound/LN_stop_speaker.py | 3 +- .../logicnode/string/LN_concatenate_string.py | 3 +- .../arm/logicnode/string/LN_parse_float.py | 3 +- .../arm/logicnode/string/LN_split_string.py | 3 +- blender/arm/logicnode/string/LN_string.py | 3 +- .../arm/logicnode/string/LN_string_case.py | 3 +- .../logicnode/string/LN_string_contains.py | 3 +- .../arm/logicnode/string/LN_string_length.py | 3 +- blender/arm/logicnode/string/LN_sub_string.py | 3 +- .../logicnode/trait/LN_add_trait_to_object.py | 3 +- .../logicnode/trait/LN_get_object_trait.py | 3 +- .../logicnode/trait/LN_get_object_traits.py | 3 +- .../arm/logicnode/trait/LN_get_trait_name.py | 3 +- .../logicnode/trait/LN_get_trait_paused.py | 3 +- .../arm/logicnode/trait/LN_remove_trait.py | 3 +- blender/arm/logicnode/trait/LN_self_trait.py | 3 +- .../logicnode/trait/LN_set_trait_paused.py | 3 +- blender/arm/logicnode/trait/LN_trait.py | 3 +- .../transform/LN_append_transform.py | 3 +- .../transform/LN_get_object_location.py | 3 +- .../transform/LN_get_object_rotation.py | 3 +- .../transform/LN_get_object_scale.py | 3 +- .../transform/LN_get_object_transform.py | 3 +- .../transform/LN_get_world_orientation.py | 3 +- blender/arm/logicnode/transform/LN_look_at.py | 3 +- .../logicnode/transform/LN_rotate_object.py | 3 +- .../transform/LN_separate_transform.py | 3 +- .../transform/LN_set_object_location.py | 3 +- .../transform/LN_set_object_rotation.py | 3 +- .../transform/LN_set_object_scale.py | 3 +- .../transform/LN_set_object_transform.py | 3 +- .../arm/logicnode/transform/LN_transform.py | 3 +- .../logicnode/transform/LN_transform_math.py | 3 +- .../transform/LN_transform_to_vector.py | 3 +- .../transform/LN_translate_object.py | 3 +- .../transform/LN_translate_on_local_axis.py | 3 +- .../LN_vector_to_object_orientation.py | 5 ++-- .../LN_world_vector_to_local_space.py | 5 ++-- blender/arm/logicnode/variable/LN_boolean.py | 3 +- blender/arm/logicnode/variable/LN_color.py | 3 +- blender/arm/logicnode/variable/LN_dynamic.py | 3 +- blender/arm/logicnode/variable/LN_float.py | 3 +- blender/arm/logicnode/variable/LN_integer.py | 3 +- blender/arm/logicnode/variable/LN_mask.py | 3 +- .../arm/logicnode/variable/LN_quaternion.py | 3 +- .../arm/logicnode/variable/LN_set_variable.py | 3 +- blender/arm/logicnode/variable/LN_vector.py | 3 +- blender/arm/make_logic.py | 3 +- blender/arm/node_utils.py | 2 +- blender/arm/write_data.py | 3 ++ 333 files changed, 396 insertions(+), 666 deletions(-) diff --git a/Sources/armory/logicnode/LogicNode.hx b/Sources/armory/logicnode/LogicNode.hx index 9255a559..38dac54b 100644 --- a/Sources/armory/logicnode/LogicNode.hx +++ b/Sources/armory/logicnode/LogicNode.hx @@ -1,5 +1,6 @@ package armory.logicnode; +#if arm_patch @:keep @:keepSub #end class LogicNode { var tree: LogicTree; diff --git a/Sources/armory/trait/internal/LivePatch.hx b/Sources/armory/trait/internal/LivePatch.hx index 69c33452..550acb5b 100644 --- a/Sources/armory/trait/internal/LivePatch.hx +++ b/Sources/armory/trait/internal/LivePatch.hx @@ -6,6 +6,7 @@ import armory.logicnode.LogicTree; #if arm_patch @:expose("LivePatch") #end @:access(armory.logicnode.LogicNode) +@:access(armory.logicnode.LogicNodeInput) class LivePatch extends iron.Trait { #if !arm_patch @@ -105,6 +106,31 @@ class LivePatch extends iron.Trait { tree.nodes.remove(nodeName); } + public static function patchNodeCreate(treeName: String, nodeName: String, nodeType: String, propDatas: Array>, inputDatas: Array>, outputDatas: Array>) { + var tree = LogicTree.nodeTrees[treeName]; + if (tree == null) return; + + // No further constructor parameters required here, all variable nodes + // use optional further parameters and all values are set later in this + // function. + var newNode: LogicNode = Type.createInstance(Type.resolveClass(nodeType), [tree]); + newNode.name = nodeName; + tree.nodes[nodeName] = newNode; + + for (propData in propDatas) { + Reflect.setField(newNode, propData[0], propData[1]); + } + + var i = 0; + for (inputData in inputDatas) { + newNode.addInput(createSocketDefaultNode(newNode.tree, inputData[0], inputData[1]), i++); + } + + for (outputData in outputDatas) { + newNode.addOutputs([createSocketDefaultNode(newNode.tree, outputData[0], outputData[1])]); + } + } + public static function patchNodeCopy(treeName: String, nodeName: String, newNodeName: String, copyProps: Array, inputDatas: Array>, outputDatas: Array>) { var tree = LogicTree.nodeTrees[treeName]; if (tree == null) return; @@ -115,7 +141,7 @@ class LivePatch extends iron.Trait { // No further constructor parameters required here, all variable nodes // use optional further parameters and all values are set later in this // function. - var newNode = Type.createInstance(Type.getClass(node), [tree]); + var newNode: LogicNode = Type.createInstance(Type.getClass(node), [tree]); newNode.name = newNodeName; tree.nodes[newNodeName] = newNode; diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index 1130e5b5..7bc8073d 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -186,7 +186,7 @@ def send_event(event_id: str, opt_data: Any = None): tree_name = arm.node_utils.get_export_tree_name(node.get_tree()) node_name = arm.node_utils.get_export_node_name(node)[1:] - value = arm.node_utils.haxe_format_prop(node, prop_name) + value = arm.node_utils.haxe_format_prop_value(node, prop_name) js = f'LivePatch.patchUpdateNodeProp("{tree_name}", "{node_name}", "{prop_name}", {value});' write_patch(js) @@ -215,6 +215,25 @@ def send_event(event_id: str, opt_data: Any = None): js = f'LivePatch.patchUpdateNodeInputVal("{tree_name}", "{node_name}", {socket_index}, {value});' write_patch(js) + elif event_id == 'ln_create': + node: ArmLogicTreeNode = opt_data + + tree_name = arm.node_utils.get_export_tree_name(node.get_tree()) + node_name = arm.node_utils.get_export_node_name(node)[1:] + node_type = 'armory.logicnode.' + node.bl_idname[2:] + + prop_names = (p for p in arm.node_utils.get_haxe_property_names(node)) + prop_values = (getattr(node, prop_name) for prop_name in prop_names) + prop_datas = arm.node_utils.haxe_format_socket_val(list(zip(prop_names, prop_values))) + + inp_data = [(inp.arm_socket_type, inp.get_default_value()) for inp in node.inputs] + inp_data = arm.node_utils.haxe_format_socket_val(inp_data) + out_data = [(out.arm_socket_type, out.get_default_value()) for out in node.outputs] + out_data = arm.node_utils.haxe_format_socket_val(out_data) + + js = f'LivePatch.patchNodeCreate("{tree_name}", "{node_name}", "{node_type}", {prop_datas}, {inp_data}, {out_data});' + write_patch(js) + elif event_id == 'ln_delete': node: ArmLogicTreeNode = opt_data diff --git a/blender/arm/logicnode/animation/LN_action.py b/blender/arm/logicnode/animation/LN_action.py index d156819b..e7913910 100644 --- a/blender/arm/logicnode/animation/LN_action.py +++ b/blender/arm/logicnode/animation/LN_action.py @@ -6,8 +6,7 @@ class AnimActionNode(ArmLogicTreeNode): bl_label = 'Action' arm_version = 1 - def init(self, context): - super(AnimActionNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAnimAction', 'Action') self.add_output('ArmNodeSocketAnimAction', 'Action', is_var=True) diff --git a/blender/arm/logicnode/animation/LN_blend_action.py b/blender/arm/logicnode/animation/LN_blend_action.py index 0d98d693..0214e28d 100644 --- a/blender/arm/logicnode/animation/LN_blend_action.py +++ b/blender/arm/logicnode/animation/LN_blend_action.py @@ -6,8 +6,7 @@ class BlendActionNode(ArmLogicTreeNode): bl_label = 'Blend Action' arm_version = 1 - def init(self, context): - super(BlendActionNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmNodeSocketAnimAction', 'Action 1') diff --git a/blender/arm/logicnode/animation/LN_bone_fk.py b/blender/arm/logicnode/animation/LN_bone_fk.py index 58f59910..d994584d 100644 --- a/blender/arm/logicnode/animation/LN_bone_fk.py +++ b/blender/arm/logicnode/animation/LN_bone_fk.py @@ -7,8 +7,7 @@ class BoneFKNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'armature' - def init(self, context): - super(BoneFKNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmStringSocket', 'Bone') diff --git a/blender/arm/logicnode/animation/LN_bone_ik.py b/blender/arm/logicnode/animation/LN_bone_ik.py index 00577537..e677b9d8 100644 --- a/blender/arm/logicnode/animation/LN_bone_ik.py +++ b/blender/arm/logicnode/animation/LN_bone_ik.py @@ -7,8 +7,7 @@ class BoneIKNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'armature' - def init(self, context): - super(BoneIKNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmStringSocket', 'Bone') diff --git a/blender/arm/logicnode/animation/LN_get_action_state.py b/blender/arm/logicnode/animation/LN_get_action_state.py index 51e2d130..1b506613 100644 --- a/blender/arm/logicnode/animation/LN_get_action_state.py +++ b/blender/arm/logicnode/animation/LN_get_action_state.py @@ -6,8 +6,7 @@ class AnimationStateNode(ArmLogicTreeNode): bl_label = 'Get Action State' arm_version = 1 - def init(self, context): - super(AnimationStateNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') self.add_output('ArmStringSocket', 'Action') diff --git a/blender/arm/logicnode/animation/LN_get_tilesheet_state.py b/blender/arm/logicnode/animation/LN_get_tilesheet_state.py index dadf0888..09adee81 100644 --- a/blender/arm/logicnode/animation/LN_get_tilesheet_state.py +++ b/blender/arm/logicnode/animation/LN_get_tilesheet_state.py @@ -7,8 +7,7 @@ class GetTilesheetStateNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'tilesheet' - def init(self, context): - super(GetTilesheetStateNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') self.add_output('ArmStringSocket', 'Name') diff --git a/blender/arm/logicnode/animation/LN_on_action_marker.py b/blender/arm/logicnode/animation/LN_on_action_marker.py index c26abc19..07c4e690 100644 --- a/blender/arm/logicnode/animation/LN_on_action_marker.py +++ b/blender/arm/logicnode/animation/LN_on_action_marker.py @@ -6,8 +6,7 @@ class OnActionMarkerNode(ArmLogicTreeNode): bl_label = 'On Action Marker' arm_version = 1 - def init(self, context): - super(OnActionMarkerNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmStringSocket', 'Marker') diff --git a/blender/arm/logicnode/animation/LN_play_action_from.py b/blender/arm/logicnode/animation/LN_play_action_from.py index 732a7c40..31a218ea 100644 --- a/blender/arm/logicnode/animation/LN_play_action_from.py +++ b/blender/arm/logicnode/animation/LN_play_action_from.py @@ -6,8 +6,7 @@ class PlayActionFromNode(ArmLogicTreeNode): bl_label = 'Play Action From' arm_version = 2 - def init(self, context): - super(PlayActionFromNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmNodeSocketAnimAction', 'Action') @@ -22,7 +21,7 @@ class PlayActionFromNode(ArmLogicTreeNode): def get_replacement_node(self, node_tree: bpy.types.NodeTree): if self.arm_version not in (0, 1): raise LookupError() - + return NodeReplacement( 'LNPlayActionFromNode', self.arm_version, 'LNPlayActionFromNode', 2, in_socket_mapping={0:0, 1:1, 2:2, 3:3, 4:4}, out_socket_mapping={0:0, 1:1} diff --git a/blender/arm/logicnode/animation/LN_play_tilesheet.py b/blender/arm/logicnode/animation/LN_play_tilesheet.py index 4af5d10a..6e008f9e 100644 --- a/blender/arm/logicnode/animation/LN_play_tilesheet.py +++ b/blender/arm/logicnode/animation/LN_play_tilesheet.py @@ -7,8 +7,7 @@ class PlayTilesheetNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'tilesheet' - def init(self, context): - super(PlayTilesheetNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmStringSocket', 'Name') diff --git a/blender/arm/logicnode/animation/LN_set_action_paused.py b/blender/arm/logicnode/animation/LN_set_action_paused.py index 8df9b9ce..6c994959 100644 --- a/blender/arm/logicnode/animation/LN_set_action_paused.py +++ b/blender/arm/logicnode/animation/LN_set_action_paused.py @@ -6,8 +6,7 @@ class SetActionPausedNode(ArmLogicTreeNode): bl_label = 'Set Action Paused' arm_version = 1 - def init(self, context): - super(SetActionPausedNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmBoolSocket', 'Paused') diff --git a/blender/arm/logicnode/animation/LN_set_action_speed.py b/blender/arm/logicnode/animation/LN_set_action_speed.py index 9a011883..e90cec67 100644 --- a/blender/arm/logicnode/animation/LN_set_action_speed.py +++ b/blender/arm/logicnode/animation/LN_set_action_speed.py @@ -6,8 +6,7 @@ class SetActionSpeedNode(ArmLogicTreeNode): bl_label = 'Set Action Speed' arm_version = 1 - def init(self, context): - super(SetActionSpeedNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmFloatSocket', 'Speed', default_value=1.0) diff --git a/blender/arm/logicnode/animation/LN_set_parent_bone.py b/blender/arm/logicnode/animation/LN_set_parent_bone.py index dc40384b..5aa686e3 100644 --- a/blender/arm/logicnode/animation/LN_set_parent_bone.py +++ b/blender/arm/logicnode/animation/LN_set_parent_bone.py @@ -7,8 +7,7 @@ class SetParentBoneNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'armature' - def init(self, context): - super(SetParentBoneNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmNodeSocketObject', 'Parent', default_value='Parent') diff --git a/blender/arm/logicnode/animation/LN_set_particle_speed.py b/blender/arm/logicnode/animation/LN_set_particle_speed.py index d4e614bd..2fe92ee4 100644 --- a/blender/arm/logicnode/animation/LN_set_particle_speed.py +++ b/blender/arm/logicnode/animation/LN_set_particle_speed.py @@ -6,8 +6,7 @@ class SetParticleSpeedNode(ArmLogicTreeNode): bl_label = 'Set Particle Speed' arm_version = 1 - def init(self, context): - super(SetParticleSpeedNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmFloatSocket', 'Speed', default_value=1.0) diff --git a/blender/arm/logicnode/animation/LN_set_tilesheet_paused.py b/blender/arm/logicnode/animation/LN_set_tilesheet_paused.py index ef7f954a..393a259f 100644 --- a/blender/arm/logicnode/animation/LN_set_tilesheet_paused.py +++ b/blender/arm/logicnode/animation/LN_set_tilesheet_paused.py @@ -7,8 +7,7 @@ class SetTilesheetPausedNode(ArmLogicTreeNode): arm_section = 'tilesheet' arm_version = 1 - def init(self, context): - super(SetTilesheetPausedNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmBoolSocket', 'Paused') diff --git a/blender/arm/logicnode/arm_nodes.py b/blender/arm/logicnode/arm_nodes.py index 50db2c43..d7548c5c 100644 --- a/blender/arm/logicnode/arm_nodes.py +++ b/blender/arm/logicnode/arm_nodes.py @@ -37,6 +37,14 @@ class ArmLogicTreeNode(bpy.types.Node): else: self.arm_version = 1 + if not hasattr(self, 'arm_init'): + # Show warning for older node packages + arm.log.warn(f'Node {self.bl_idname} has no arm_init function and might not work correctly!') + else: + self.arm_init(context) + + arm.live_patch.send_event('ln_create', self) + @classmethod def poll(cls, ntree): return ntree.bl_idname == 'ArmLogicTreeType' diff --git a/blender/arm/logicnode/array/LN_array.py b/blender/arm/logicnode/array/LN_array.py index 39326e79..65473756 100644 --- a/blender/arm/logicnode/array/LN_array.py +++ b/blender/arm/logicnode/array/LN_array.py @@ -10,8 +10,7 @@ class ArrayNode(ArmLogicTreeNode): def __init__(self): array_nodes[str(id(self))] = self - def init(self, context): - super(ArrayNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketArray', 'Array', is_var=True) self.add_output('ArmIntSocket', 'Length') diff --git a/blender/arm/logicnode/array/LN_array_add.py b/blender/arm/logicnode/array/LN_array_add.py index acbc5959..0fc31a6d 100644 --- a/blender/arm/logicnode/array/LN_array_add.py +++ b/blender/arm/logicnode/array/LN_array_add.py @@ -15,8 +15,7 @@ class ArrayAddNode(ArmLogicTreeNode): super(ArrayAddNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(ArrayAddNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketArray', 'Array') self.add_input('ArmBoolSocket', 'Modify Original', default_value=True) diff --git a/blender/arm/logicnode/array/LN_array_boolean.py b/blender/arm/logicnode/array/LN_array_boolean.py index ae355b09..7c0b333d 100644 --- a/blender/arm/logicnode/array/LN_array_boolean.py +++ b/blender/arm/logicnode/array/LN_array_boolean.py @@ -11,8 +11,7 @@ class BooleanArrayNode(ArmLogicTreeNode): super(BooleanArrayNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(BooleanArrayNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketArray', 'Array', is_var=True) self.add_output('ArmIntSocket', 'Length') diff --git a/blender/arm/logicnode/array/LN_array_color.py b/blender/arm/logicnode/array/LN_array_color.py index f569245a..148fd2da 100644 --- a/blender/arm/logicnode/array/LN_array_color.py +++ b/blender/arm/logicnode/array/LN_array_color.py @@ -11,8 +11,7 @@ class ColorArrayNode(ArmLogicTreeNode): super(ColorArrayNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(ColorArrayNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketArray', 'Array', is_var=True) self.add_output('ArmIntSocket', 'Length') diff --git a/blender/arm/logicnode/array/LN_array_contains.py b/blender/arm/logicnode/array/LN_array_contains.py index b0a2bc53..c81ec46a 100644 --- a/blender/arm/logicnode/array/LN_array_contains.py +++ b/blender/arm/logicnode/array/LN_array_contains.py @@ -6,8 +6,7 @@ class ArrayContainsNode(ArmLogicTreeNode): bl_label = 'Array Contains' arm_version = 1 - def init(self, context): - super(ArrayContainsNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketArray', 'Array') self.add_input('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/array/LN_array_float.py b/blender/arm/logicnode/array/LN_array_float.py index 22e2019b..2c3ec3d8 100644 --- a/blender/arm/logicnode/array/LN_array_float.py +++ b/blender/arm/logicnode/array/LN_array_float.py @@ -11,8 +11,7 @@ class FloatArrayNode(ArmLogicTreeNode): super(FloatArrayNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(FloatArrayNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketArray', 'Array', is_var=True) self.add_output('ArmIntSocket', 'Length') diff --git a/blender/arm/logicnode/array/LN_array_get.py b/blender/arm/logicnode/array/LN_array_get.py index bda50633..598a60f5 100644 --- a/blender/arm/logicnode/array/LN_array_get.py +++ b/blender/arm/logicnode/array/LN_array_get.py @@ -6,8 +6,7 @@ class ArrayGetNode(ArmLogicTreeNode): bl_label = 'Array Get' arm_version = 1 - def init(self, context): - super(ArrayGetNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketArray', 'Array') self.add_input('ArmIntSocket', 'Index') diff --git a/blender/arm/logicnode/array/LN_array_integer.py b/blender/arm/logicnode/array/LN_array_integer.py index f4e0aab8..1b01e721 100644 --- a/blender/arm/logicnode/array/LN_array_integer.py +++ b/blender/arm/logicnode/array/LN_array_integer.py @@ -11,8 +11,7 @@ class IntegerArrayNode(ArmLogicTreeNode): super(IntegerArrayNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(IntegerArrayNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketArray', 'Array') self.add_output('ArmIntSocket', 'Length') diff --git a/blender/arm/logicnode/array/LN_array_length.py b/blender/arm/logicnode/array/LN_array_length.py index c655ac86..3ef4e9f1 100644 --- a/blender/arm/logicnode/array/LN_array_length.py +++ b/blender/arm/logicnode/array/LN_array_length.py @@ -6,8 +6,7 @@ class ArrayLengthNode(ArmLogicTreeNode): bl_label = 'Array Length' arm_version = 1 - def init(self, context): - super(ArrayLengthNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketArray', 'Array') self.add_output('ArmIntSocket', 'Length') diff --git a/blender/arm/logicnode/array/LN_array_loop_node.py b/blender/arm/logicnode/array/LN_array_loop_node.py index 18207c8e..aefd7331 100644 --- a/blender/arm/logicnode/array/LN_array_loop_node.py +++ b/blender/arm/logicnode/array/LN_array_loop_node.py @@ -7,8 +7,7 @@ class ArrayLoopNode(ArmLogicTreeNode): bl_label = 'Array Loop' arm_version = 1 - def init(self, context): - super(ArrayLoopNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketArray', 'Array') diff --git a/blender/arm/logicnode/array/LN_array_object.py b/blender/arm/logicnode/array/LN_array_object.py index 71c55d89..513e211c 100644 --- a/blender/arm/logicnode/array/LN_array_object.py +++ b/blender/arm/logicnode/array/LN_array_object.py @@ -11,8 +11,7 @@ class ObjectArrayNode(ArmLogicTreeNode): super(ObjectArrayNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(ObjectArrayNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketArray', 'Array', is_var=True) self.add_output('ArmIntSocket', 'Length') diff --git a/blender/arm/logicnode/array/LN_array_pop.py b/blender/arm/logicnode/array/LN_array_pop.py index 9708b343..37b9ad5c 100644 --- a/blender/arm/logicnode/array/LN_array_pop.py +++ b/blender/arm/logicnode/array/LN_array_pop.py @@ -8,8 +8,7 @@ class ArrayPopNode(ArmLogicTreeNode): bl_label = 'Array Pop' arm_version = 1 - def init(self, context): - super(ArrayPopNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketArray', 'Array') self.add_output('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/array/LN_array_remove_by_index.py b/blender/arm/logicnode/array/LN_array_remove_by_index.py index a07c864c..efc69b43 100644 --- a/blender/arm/logicnode/array/LN_array_remove_by_index.py +++ b/blender/arm/logicnode/array/LN_array_remove_by_index.py @@ -8,8 +8,7 @@ class ArrayRemoveIndexNode(ArmLogicTreeNode): bl_label = 'Array Remove by Index' arm_version = 1 - def init(self, context): - super(ArrayRemoveIndexNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketArray', 'Array') self.add_input('ArmIntSocket', 'Index') diff --git a/blender/arm/logicnode/array/LN_array_remove_by_value.py b/blender/arm/logicnode/array/LN_array_remove_by_value.py index 8ab646fd..5314b714 100644 --- a/blender/arm/logicnode/array/LN_array_remove_by_value.py +++ b/blender/arm/logicnode/array/LN_array_remove_by_value.py @@ -11,8 +11,7 @@ class ArrayRemoveValueNode(ArmLogicTreeNode): # def __init__(self): # array_nodes[str(id(self))] = self - def init(self, context): - super(ArrayRemoveValueNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketArray', 'Array') self.add_input('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/array/LN_array_set.py b/blender/arm/logicnode/array/LN_array_set.py index c0fb2293..e1eac977 100644 --- a/blender/arm/logicnode/array/LN_array_set.py +++ b/blender/arm/logicnode/array/LN_array_set.py @@ -6,8 +6,7 @@ class ArraySetNode(ArmLogicTreeNode): bl_label = 'Array Set' arm_version = 1 - def init(self, context): - super(ArraySetNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketArray', 'Array') self.add_input('ArmIntSocket', 'Index') diff --git a/blender/arm/logicnode/array/LN_array_shift.py b/blender/arm/logicnode/array/LN_array_shift.py index d1d31b37..15ee7b78 100644 --- a/blender/arm/logicnode/array/LN_array_shift.py +++ b/blender/arm/logicnode/array/LN_array_shift.py @@ -8,8 +8,7 @@ class ArrayShiftNode(ArmLogicTreeNode): bl_label = 'Array Shift' arm_version = 1 - def init(self, context): - super(ArrayShiftNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketArray', 'Array') self.add_output('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/array/LN_array_slice.py b/blender/arm/logicnode/array/LN_array_slice.py index 87da4910..be2b1bd4 100644 --- a/blender/arm/logicnode/array/LN_array_slice.py +++ b/blender/arm/logicnode/array/LN_array_slice.py @@ -8,8 +8,7 @@ class ArraySliceNode(ArmLogicTreeNode): bl_label = 'Array Slice' arm_version = 1 - def init(self, context): - super(ArraySliceNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketArray', 'Array') self.add_input('ArmIntSocket', 'Index') self.add_input('ArmIntSocket', 'End') diff --git a/blender/arm/logicnode/array/LN_array_splice.py b/blender/arm/logicnode/array/LN_array_splice.py index ea1c28f8..1f367cc5 100644 --- a/blender/arm/logicnode/array/LN_array_splice.py +++ b/blender/arm/logicnode/array/LN_array_splice.py @@ -8,8 +8,7 @@ class ArraySpliceNode(ArmLogicTreeNode): bl_label = 'Array Splice' arm_version = 1 - def init(self, context): - super(ArraySpliceNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketArray', 'Array') self.add_input('ArmIntSocket', 'Index') diff --git a/blender/arm/logicnode/array/LN_array_string.py b/blender/arm/logicnode/array/LN_array_string.py index 2052a9db..0bf6ed27 100644 --- a/blender/arm/logicnode/array/LN_array_string.py +++ b/blender/arm/logicnode/array/LN_array_string.py @@ -11,8 +11,7 @@ class StringArrayNode(ArmLogicTreeNode): super(StringArrayNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(StringArrayNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketArray', 'Array', is_var=True) self.add_output('ArmIntSocket', 'Length') diff --git a/blender/arm/logicnode/array/LN_array_vector.py b/blender/arm/logicnode/array/LN_array_vector.py index 75b08e59..bf1715d7 100644 --- a/blender/arm/logicnode/array/LN_array_vector.py +++ b/blender/arm/logicnode/array/LN_array_vector.py @@ -11,8 +11,7 @@ class VectorArrayNode(ArmLogicTreeNode): super(VectorArrayNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(VectorArrayNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketArray', 'Array', is_var=True) self.add_output('ArmIntSocket', 'Length') diff --git a/blender/arm/logicnode/camera/LN_get_camera_active.py b/blender/arm/logicnode/camera/LN_get_camera_active.py index 54c3fe7a..9956d45e 100644 --- a/blender/arm/logicnode/camera/LN_get_camera_active.py +++ b/blender/arm/logicnode/camera/LN_get_camera_active.py @@ -8,6 +8,5 @@ class ActiveCameraNode(ArmLogicTreeNode): bl_label = 'Get Camera Active' arm_version = 1 - def init(self, context): - super(ActiveCameraNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketObject', 'Camera') diff --git a/blender/arm/logicnode/camera/LN_get_camera_fov.py b/blender/arm/logicnode/camera/LN_get_camera_fov.py index f6dc4426..b5e70466 100644 --- a/blender/arm/logicnode/camera/LN_get_camera_fov.py +++ b/blender/arm/logicnode/camera/LN_get_camera_fov.py @@ -8,8 +8,7 @@ class GetCameraFovNode(ArmLogicTreeNode): bl_label = 'Get Camera FOV' arm_version = 1 - def init(self, context): - super(GetCameraFovNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') self.add_output('ArmFloatSocket', 'FOV') diff --git a/blender/arm/logicnode/camera/LN_set_camera_active.py b/blender/arm/logicnode/camera/LN_set_camera_active.py index dd05a919..fa078d10 100644 --- a/blender/arm/logicnode/camera/LN_set_camera_active.py +++ b/blender/arm/logicnode/camera/LN_set_camera_active.py @@ -8,8 +8,7 @@ class SetCameraNode(ArmLogicTreeNode): bl_label = 'Set Camera Active' arm_version = 1 - def init(self, context): - super(SetCameraNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Camera') diff --git a/blender/arm/logicnode/camera/LN_set_camera_fov.py b/blender/arm/logicnode/camera/LN_set_camera_fov.py index 2a5103ea..1974c0d0 100644 --- a/blender/arm/logicnode/camera/LN_set_camera_fov.py +++ b/blender/arm/logicnode/camera/LN_set_camera_fov.py @@ -8,8 +8,7 @@ class SetCameraFovNode(ArmLogicTreeNode): bl_label = 'Set Camera FOV' arm_version = 1 - def init(self, context): - super(SetCameraFovNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Camera') self.add_input('ArmFloatSocket', 'FOV', default_value=0.9) diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_checkbox.py b/blender/arm/logicnode/canvas/LN_get_canvas_checkbox.py index a8860a36..de3cee1d 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_checkbox.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_checkbox.py @@ -6,8 +6,7 @@ class CanvasGetCheckboxNode(ArmLogicTreeNode): bl_label = 'Get Canvas Checkbox' arm_version = 1 - def init(self, context): - super(CanvasGetCheckboxNode, self).init(context) + def arm_init(self, context): self.add_input('ArmStringSocket', 'Element') self.add_output('ArmBoolSocket', 'Is Checked') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_input_text.py b/blender/arm/logicnode/canvas/LN_get_canvas_input_text.py index 69938c5c..e8d1a7d3 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_input_text.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_input_text.py @@ -6,8 +6,7 @@ class CanvasGetInputTextNode(ArmLogicTreeNode): bl_label = 'Get Canvas Input Text' arm_version = 1 - def init(self, context): - super(CanvasGetInputTextNode, self).init(context) + def arm_init(self, context): self.add_input('ArmStringSocket', 'Element') self.add_output('ArmStringSocket', 'Text') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_location.py b/blender/arm/logicnode/canvas/LN_get_canvas_location.py index 21e38a1b..71e9491f 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_location.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_location.py @@ -6,8 +6,7 @@ class CanvasGetLocationNode(ArmLogicTreeNode): bl_label = 'Get Canvas Location' arm_version = 1 - def init(self, context): - super(CanvasGetLocationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'Element') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_position.py b/blender/arm/logicnode/canvas/LN_get_canvas_position.py index 66ae4db6..47efa750 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_position.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_position.py @@ -6,8 +6,7 @@ class CanvasGetPositionNode(ArmLogicTreeNode): bl_label = 'Get Canvas Position' arm_version = 1 - def init(self, context): - super(CanvasGetPositionNode, self).init(context) + def arm_init(self, context): self.add_input('ArmStringSocket', 'Element') self.add_output('ArmIntSocket', 'Position') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_progress_bar.py b/blender/arm/logicnode/canvas/LN_get_canvas_progress_bar.py index 493a740d..5e9435ca 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_progress_bar.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_progress_bar.py @@ -6,8 +6,7 @@ class CanvasGetPBNode(ArmLogicTreeNode): bl_label = 'Get Canvas Progress Bar' arm_version = 1 - def init(self, context): - super(CanvasGetPBNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'Element') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_rotation.py b/blender/arm/logicnode/canvas/LN_get_canvas_rotation.py index bde56b39..3b7ffa9e 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_rotation.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_rotation.py @@ -6,8 +6,7 @@ class CanvasGetRotationNode(ArmLogicTreeNode): bl_label = 'Get Canvas Rotation' arm_version = 1 - def init(self, context): - super(CanvasGetRotationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'Element') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_scale.py b/blender/arm/logicnode/canvas/LN_get_canvas_scale.py index fad278f2..9e65ea39 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_scale.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_scale.py @@ -6,8 +6,7 @@ class CanvasGetScaleNode(ArmLogicTreeNode): bl_label = 'Get Canvas Scale' arm_version = 1 - def init(self, context): - super(CanvasGetScaleNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'Element') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_slider.py b/blender/arm/logicnode/canvas/LN_get_canvas_slider.py index d6d44535..c0d09cc4 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_slider.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_slider.py @@ -6,8 +6,7 @@ class CanvasGetSliderNode(ArmLogicTreeNode): bl_label = 'Get Canvas Slider' arm_version = 1 - def init(self, context): - super(CanvasGetSliderNode, self).init(context) + def arm_init(self, context): self.add_input('ArmStringSocket', 'Element') self.add_output('ArmFloatSocket', 'Float') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_visible.py b/blender/arm/logicnode/canvas/LN_get_canvas_visible.py index f930146f..3fb4ae1b 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_visible.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_visible.py @@ -9,8 +9,7 @@ class CanvasGetVisibleNode(ArmLogicTreeNode): bl_label = 'Get Canvas Visible' arm_version = 1 - def init(self, context): - super(CanvasGetVisibleNode, self).init(context) + def arm_init(self, context): self.inputs.new('ArmStringSocket', 'Element') self.outputs.new('ArmBoolSocket', 'Is Visible') diff --git a/blender/arm/logicnode/canvas/LN_on_canvas_element.py b/blender/arm/logicnode/canvas/LN_on_canvas_element.py index a72501a5..fe65ad0c 100644 --- a/blender/arm/logicnode/canvas/LN_on_canvas_element.py +++ b/blender/arm/logicnode/canvas/LN_on_canvas_element.py @@ -24,8 +24,7 @@ class OnCanvasElementNode(ArmLogicTreeNode): ('right', 'Right', 'Right mouse button')], name='Mouse Button', default='left') - def init(self, context): - super(OnCanvasElementNode, self).init(context) + def arm_init(self, context): self.add_input('ArmStringSocket', 'Element') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_asset.py b/blender/arm/logicnode/canvas/LN_set_canvas_asset.py index 077ece2e..bf734e70 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_asset.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_asset.py @@ -6,8 +6,7 @@ class CanvasSetAssetNode(ArmLogicTreeNode): bl_label = 'Set Canvas Asset' arm_version = 1 - def init(self, context): - super(CanvasSetAssetNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'Element') self.add_input('ArmStringSocket', 'Asset') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_checkbox.py b/blender/arm/logicnode/canvas/LN_set_canvas_checkbox.py index 293200a5..ad41a7ae 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_checkbox.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_checkbox.py @@ -6,8 +6,7 @@ class CanvasSetCheckBoxNode(ArmLogicTreeNode): bl_label = 'Set Canvas Checkbox' arm_version = 1 - def init(self, context): - super(CanvasSetCheckBoxNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'Element') self.add_input('ArmBoolSocket', 'Check') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_location.py b/blender/arm/logicnode/canvas/LN_set_canvas_location.py index cfb43449..03402117 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_location.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_location.py @@ -6,8 +6,7 @@ class CanvasSetLocationNode(ArmLogicTreeNode): bl_label = 'Set Canvas Location' arm_version = 1 - def init(self, context): - super(CanvasSetLocationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'Element') self.add_input('ArmFloatSocket', 'X') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_progress_bar.py b/blender/arm/logicnode/canvas/LN_set_canvas_progress_bar.py index 38ae0530..b5d59085 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_progress_bar.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_progress_bar.py @@ -6,8 +6,7 @@ class CanvasSetPBNode(ArmLogicTreeNode): bl_label = 'Set Canvas Progress Bar' arm_version = 1 - def init(self, context): - super(CanvasSetPBNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'Element') self.add_input('ArmIntSocket', 'At') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_rotation.py b/blender/arm/logicnode/canvas/LN_set_canvas_rotation.py index dae5deaf..ee9f6502 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_rotation.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_rotation.py @@ -6,8 +6,7 @@ class CanvasSetRotationNode(ArmLogicTreeNode): bl_label = 'Set Canvas Rotation' arm_version = 1 - def init(self, context): - super(CanvasSetRotationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'Element') self.add_input('ArmFloatSocket', 'Rad') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_scale.py b/blender/arm/logicnode/canvas/LN_set_canvas_scale.py index 714c0030..7afbf7ab 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_scale.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_scale.py @@ -6,8 +6,7 @@ class CanvasSetScaleNode(ArmLogicTreeNode): bl_label = 'Set Canvas Scale' arm_version = 1 - def init(self, context): - super(CanvasSetScaleNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'Element') self.add_input('ArmIntSocket', 'Height') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_slider.py b/blender/arm/logicnode/canvas/LN_set_canvas_slider.py index 7f48cce3..9988266b 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_slider.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_slider.py @@ -6,8 +6,7 @@ class CanvasSetSliderNode(ArmLogicTreeNode): bl_label = 'Set Canvas Slider' arm_version = 1 - def init(self, context): - super(CanvasSetSliderNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'Element') self.add_input('ArmFloatSocket', 'Float') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_text.py b/blender/arm/logicnode/canvas/LN_set_canvas_text.py index a706b31a..462b7ba2 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_text.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_text.py @@ -6,8 +6,7 @@ class CanvasSetTextNode(ArmLogicTreeNode): bl_label = 'Set Canvas Text' arm_version = 1 - def init(self, context): - super(CanvasSetTextNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'Element') self.add_input('ArmStringSocket', 'Text') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_text_color.py b/blender/arm/logicnode/canvas/LN_set_canvas_text_color.py index b6064691..5863510f 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_text_color.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_text_color.py @@ -6,8 +6,7 @@ class CanvasSetTextColorNode(ArmLogicTreeNode): bl_label = 'Set Canvas Text Color' arm_version = 1 - def init(self, context): - super(CanvasSetTextColorNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'Element') self.add_input('ArmFloatSocket', 'R') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_visible.py b/blender/arm/logicnode/canvas/LN_set_canvas_visible.py index e9af8266..dbe88b26 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_visible.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_visible.py @@ -6,8 +6,7 @@ class CanvasSetVisibleNode(ArmLogicTreeNode): bl_label = 'Set Canvas Visible' arm_version = 1 - def init(self, context): - super(CanvasSetVisibleNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'Element') self.add_input('ArmBoolSocket', 'Visible') diff --git a/blender/arm/logicnode/deprecated/LN_get_mouse_lock.py b/blender/arm/logicnode/deprecated/LN_get_mouse_lock.py index 5a651247..83cea8fa 100644 --- a/blender/arm/logicnode/deprecated/LN_get_mouse_lock.py +++ b/blender/arm/logicnode/deprecated/LN_get_mouse_lock.py @@ -14,8 +14,7 @@ class GetMouseLockNode(ArmLogicTreeNode): arm_category = 'Input' arm_section = 'mouse' - def init(self, context): - super(GetMouseLockNode, self).init(context) + def arm_init(self, context): self.outputs.new('ArmBoolSocket', 'Is Locked') def get_replacement_node(self, node_tree: bpy.types.NodeTree): diff --git a/blender/arm/logicnode/deprecated/LN_get_mouse_visible.py b/blender/arm/logicnode/deprecated/LN_get_mouse_visible.py index 6f394deb..95c5d0f1 100644 --- a/blender/arm/logicnode/deprecated/LN_get_mouse_visible.py +++ b/blender/arm/logicnode/deprecated/LN_get_mouse_visible.py @@ -14,8 +14,7 @@ class GetMouseVisibleNode(ArmLogicTreeNode): arm_section = 'mouse' arm_version = 2 - def init(self, context): - super(GetMouseVisibleNode, self).init(context) + def arm_init(self, context): self.outputs.new('ArmBoolSocket', 'Is Visible') def get_replacement_node(self, node_tree: bpy.types.NodeTree): diff --git a/blender/arm/logicnode/deprecated/LN_mouse_coords.py b/blender/arm/logicnode/deprecated/LN_mouse_coords.py index 1244730d..60b58cdd 100644 --- a/blender/arm/logicnode/deprecated/LN_mouse_coords.py +++ b/blender/arm/logicnode/deprecated/LN_mouse_coords.py @@ -11,8 +11,7 @@ class MouseCoordsNode(ArmLogicTreeNode): arm_section = 'mouse' arm_version = 2 - def init(self, context): - super(MouseCoordsNode, self).init(context) + def arm_init(self, context): self.add_output('ArmVectorSocket', 'Coords') self.add_output('ArmVectorSocket', 'Movement') self.add_output('ArmIntSocket', 'Wheel') diff --git a/blender/arm/logicnode/deprecated/LN_on_gamepad.py b/blender/arm/logicnode/deprecated/LN_on_gamepad.py index fe1f2913..3de6a284 100644 --- a/blender/arm/logicnode/deprecated/LN_on_gamepad.py +++ b/blender/arm/logicnode/deprecated/LN_on_gamepad.py @@ -42,8 +42,7 @@ class OnGamepadNode(ArmLogicTreeNode): ('touchpad', 'touchpad', 'touchpad'),], name='', default='cross') - def init(self, context): - super(OnGamepadNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') self.add_input('ArmIntSocket', 'Gamepad') diff --git a/blender/arm/logicnode/deprecated/LN_on_keyboard.py b/blender/arm/logicnode/deprecated/LN_on_keyboard.py index b5b8bbcf..77699451 100644 --- a/blender/arm/logicnode/deprecated/LN_on_keyboard.py +++ b/blender/arm/logicnode/deprecated/LN_on_keyboard.py @@ -74,8 +74,7 @@ class OnKeyboardNode(ArmLogicTreeNode): ('down', 'down', 'down'),], name='', default='space') - def init(self, context): - super(OnKeyboardNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/deprecated/LN_on_mouse.py b/blender/arm/logicnode/deprecated/LN_on_mouse.py index 487d634e..087ef551 100644 --- a/blender/arm/logicnode/deprecated/LN_on_mouse.py +++ b/blender/arm/logicnode/deprecated/LN_on_mouse.py @@ -25,8 +25,7 @@ class OnMouseNode(ArmLogicTreeNode): ('middle', 'middle', 'middle')], name='', default='left') - def init(self, context): - super(OnMouseNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/deprecated/LN_on_surface.py b/blender/arm/logicnode/deprecated/LN_on_surface.py index b906d143..706c45c6 100644 --- a/blender/arm/logicnode/deprecated/LN_on_surface.py +++ b/blender/arm/logicnode/deprecated/LN_on_surface.py @@ -19,8 +19,7 @@ class OnSurfaceNode(ArmLogicTreeNode): ('Moved', 'Moved', 'Moved')], name='', default='Touched') - def init(self, context): - super(OnSurfaceNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/deprecated/LN_on_virtual_button.py b/blender/arm/logicnode/deprecated/LN_on_virtual_button.py index e5201612..e2377d0b 100644 --- a/blender/arm/logicnode/deprecated/LN_on_virtual_button.py +++ b/blender/arm/logicnode/deprecated/LN_on_virtual_button.py @@ -19,8 +19,7 @@ class OnVirtualButtonNode(ArmLogicTreeNode): name='', default='Started') property1: HaxeStringProperty('property1', name='', default='button') - def init(self, context): - super(OnVirtualButtonNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/deprecated/LN_pause_action.py b/blender/arm/logicnode/deprecated/LN_pause_action.py index 2def9733..b515ec0d 100644 --- a/blender/arm/logicnode/deprecated/LN_pause_action.py +++ b/blender/arm/logicnode/deprecated/LN_pause_action.py @@ -10,8 +10,7 @@ class PauseActionNode(ArmLogicTreeNode): arm_category = 'Animation' arm_version = 2 - def init(self, context): - super(PauseActionNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/deprecated/LN_pause_tilesheet.py b/blender/arm/logicnode/deprecated/LN_pause_tilesheet.py index 87185558..38313026 100644 --- a/blender/arm/logicnode/deprecated/LN_pause_tilesheet.py +++ b/blender/arm/logicnode/deprecated/LN_pause_tilesheet.py @@ -11,8 +11,7 @@ class PauseTilesheetNode(ArmLogicTreeNode): arm_section = 'tilesheet' arm_version = 2 - def init(self, context): - super(PauseTilesheetNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/deprecated/LN_pause_trait.py b/blender/arm/logicnode/deprecated/LN_pause_trait.py index 7baa2d9f..b3aecd2c 100644 --- a/blender/arm/logicnode/deprecated/LN_pause_trait.py +++ b/blender/arm/logicnode/deprecated/LN_pause_trait.py @@ -10,8 +10,7 @@ class PauseTraitNode(ArmLogicTreeNode): arm_category = 'Trait' arm_version = 2 - def init(self, context): - super(PauseTraitNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmDynamicSocket', 'Trait') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/deprecated/LN_play_action.py b/blender/arm/logicnode/deprecated/LN_play_action.py index a386d7f2..5ac411c9 100644 --- a/blender/arm/logicnode/deprecated/LN_play_action.py +++ b/blender/arm/logicnode/deprecated/LN_play_action.py @@ -10,8 +10,7 @@ class PlayActionNode(ArmLogicTreeNode): arm_category = 'Animation' arm_version = 2 - def init(self, context): - super(PlayActionNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmNodeSocketAnimAction', 'Action') diff --git a/blender/arm/logicnode/deprecated/LN_resume_action.py b/blender/arm/logicnode/deprecated/LN_resume_action.py index 4df1d8dc..9af44ae0 100644 --- a/blender/arm/logicnode/deprecated/LN_resume_action.py +++ b/blender/arm/logicnode/deprecated/LN_resume_action.py @@ -10,8 +10,7 @@ class ResumeActionNode(ArmLogicTreeNode): arm_category = 'Animation' arm_version = 2 - def init(self, context): - super(ResumeActionNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/deprecated/LN_resume_tilesheet.py b/blender/arm/logicnode/deprecated/LN_resume_tilesheet.py index 68ad6da2..b807dcc9 100644 --- a/blender/arm/logicnode/deprecated/LN_resume_tilesheet.py +++ b/blender/arm/logicnode/deprecated/LN_resume_tilesheet.py @@ -10,8 +10,7 @@ class ResumeTilesheetNode(ArmLogicTreeNode): arm_category = 'Animation' arm_version = 2 - def init(self, context): - super(ResumeTilesheetNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/deprecated/LN_resume_trait.py b/blender/arm/logicnode/deprecated/LN_resume_trait.py index ccb9e183..89c15bdf 100644 --- a/blender/arm/logicnode/deprecated/LN_resume_trait.py +++ b/blender/arm/logicnode/deprecated/LN_resume_trait.py @@ -10,8 +10,7 @@ class ResumeTraitNode(ArmLogicTreeNode): arm_category = 'Trait' arm_version = 2 - def init(self, context): - super(ResumeTraitNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmDynamicSocket', 'Trait') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/deprecated/LN_rotate_object_around_axis.py b/blender/arm/logicnode/deprecated/LN_rotate_object_around_axis.py index 49fe997d..104f774b 100644 --- a/blender/arm/logicnode/deprecated/LN_rotate_object_around_axis.py +++ b/blender/arm/logicnode/deprecated/LN_rotate_object_around_axis.py @@ -11,8 +11,7 @@ class RotateObjectAroundAxisNode(ArmLogicTreeNode): arm_section = 'rotation' arm_version = 2 - def init(self, context): - super(RotateObjectAroundAxisNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmVectorSocket', 'Axis', default_value=[0, 0, 1]) diff --git a/blender/arm/logicnode/deprecated/LN_scale_object.py b/blender/arm/logicnode/deprecated/LN_scale_object.py index 58f1078f..29d5ae3d 100644 --- a/blender/arm/logicnode/deprecated/LN_scale_object.py +++ b/blender/arm/logicnode/deprecated/LN_scale_object.py @@ -11,8 +11,7 @@ class ScaleObjectNode(ArmLogicTreeNode): arm_section = 'scale' arm_version = 2 - def init(self, context): - super(ScaleObjectNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmVectorSocket', 'Scale') diff --git a/blender/arm/logicnode/deprecated/LN_set_mouse_lock.py b/blender/arm/logicnode/deprecated/LN_set_mouse_lock.py index bb5ca4bb..a27f42c3 100644 --- a/blender/arm/logicnode/deprecated/LN_set_mouse_lock.py +++ b/blender/arm/logicnode/deprecated/LN_set_mouse_lock.py @@ -11,8 +11,7 @@ class SetMouseLockNode(ArmLogicTreeNode): arm_section = 'mouse' arm_version = 2 - def init(self, context): - super(SetMouseLockNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmBoolSocket', 'Lock') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/deprecated/LN_set_mouse_visible.py b/blender/arm/logicnode/deprecated/LN_set_mouse_visible.py index 19c29d1b..73e40a5b 100644 --- a/blender/arm/logicnode/deprecated/LN_set_mouse_visible.py +++ b/blender/arm/logicnode/deprecated/LN_set_mouse_visible.py @@ -11,8 +11,7 @@ class ShowMouseNode(ArmLogicTreeNode): arm_section = 'mouse' arm_version = 2 - def init(self, context): - super(ShowMouseNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmBoolSocket', 'Show') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/deprecated/LN_set_object_material.py b/blender/arm/logicnode/deprecated/LN_set_object_material.py index 4ca6b967..5d01c135 100644 --- a/blender/arm/logicnode/deprecated/LN_set_object_material.py +++ b/blender/arm/logicnode/deprecated/LN_set_object_material.py @@ -10,8 +10,7 @@ class SetMaterialNode(ArmLogicTreeNode): arm_category = 'Material' arm_version = 2 - def init(self, context): - super(SetMaterialNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmDynamicSocket', 'Material') diff --git a/blender/arm/logicnode/deprecated/LN_surface_coords.py b/blender/arm/logicnode/deprecated/LN_surface_coords.py index 480d4d95..3fc6d1ee 100644 --- a/blender/arm/logicnode/deprecated/LN_surface_coords.py +++ b/blender/arm/logicnode/deprecated/LN_surface_coords.py @@ -12,7 +12,6 @@ class SurfaceCoordsNode(ArmLogicTreeNode): arm_is_obsolete = 'is_obsolete' arm_version = 2 - def init(self, context): - super(SurfaceCoordsNode, self).init(context) + def arm_init(self, context): self.add_output('ArmVectorSocket', 'Coords') self.add_output('ArmVectorSocket', 'Movement') diff --git a/blender/arm/logicnode/event/LN_on_application_state.py b/blender/arm/logicnode/event/LN_on_application_state.py index e624e77d..d38f8255 100644 --- a/blender/arm/logicnode/event/LN_on_application_state.py +++ b/blender/arm/logicnode/event/LN_on_application_state.py @@ -6,8 +6,7 @@ class OnApplicationStateNode(ArmLogicTreeNode): bl_label = 'On Application State' arm_version = 1 - def init(self, context): - super().init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'On Foreground') self.add_output('ArmNodeSocketAction', 'On Background') self.add_output('ArmNodeSocketAction', 'On Shutdown') diff --git a/blender/arm/logicnode/event/LN_on_event.py b/blender/arm/logicnode/event/LN_on_event.py index 99d68c06..4cb7c28b 100644 --- a/blender/arm/logicnode/event/LN_on_event.py +++ b/blender/arm/logicnode/event/LN_on_event.py @@ -12,8 +12,7 @@ class OnEventNode(ArmLogicTreeNode): property0: HaxeStringProperty('property0', name='', default='') - def init(self, context): - super(OnEventNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/event/LN_on_init.py b/blender/arm/logicnode/event/LN_on_init.py index 3c75947c..b7246258 100644 --- a/blender/arm/logicnode/event/LN_on_init.py +++ b/blender/arm/logicnode/event/LN_on_init.py @@ -6,6 +6,5 @@ class OnInitNode(ArmLogicTreeNode): bl_label = 'On Init' arm_version = 1 - def init(self, context): - super(OnInitNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/event/LN_on_timer.py b/blender/arm/logicnode/event/LN_on_timer.py index 603755d2..f5c98e3e 100644 --- a/blender/arm/logicnode/event/LN_on_timer.py +++ b/blender/arm/logicnode/event/LN_on_timer.py @@ -9,8 +9,7 @@ class OnTimerNode(ArmLogicTreeNode): bl_label = 'On Timer' arm_version = 1 - def init(self, context): - super(OnTimerNode, self).init(context) + def arm_init(self, context): self.add_input('ArmFloatSocket', 'Duration') self.add_input('ArmBoolSocket', 'Repeat') diff --git a/blender/arm/logicnode/event/LN_on_update.py b/blender/arm/logicnode/event/LN_on_update.py index 3b39d066..c73ac019 100644 --- a/blender/arm/logicnode/event/LN_on_update.py +++ b/blender/arm/logicnode/event/LN_on_update.py @@ -17,8 +17,7 @@ class OnUpdateNode(ArmLogicTreeNode): ('Physics Pre-Update', 'Physics Pre-Update', 'Physics Pre-Update')], name='On', default='Update') - def init(self, context): - super(OnUpdateNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/event/LN_send_event_to_object.py b/blender/arm/logicnode/event/LN_send_event_to_object.py index 03dd9ed1..8470a704 100644 --- a/blender/arm/logicnode/event/LN_send_event_to_object.py +++ b/blender/arm/logicnode/event/LN_send_event_to_object.py @@ -13,8 +13,7 @@ class SendEventNode(ArmLogicTreeNode): arm_section = 'custom' arm_version = 1 - def init(self, context): - super(SendEventNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'Event') self.add_input('ArmNodeSocketObject', 'Object') diff --git a/blender/arm/logicnode/event/LN_send_global_event.py b/blender/arm/logicnode/event/LN_send_global_event.py index 25943b51..aba0ac0e 100644 --- a/blender/arm/logicnode/event/LN_send_global_event.py +++ b/blender/arm/logicnode/event/LN_send_global_event.py @@ -12,8 +12,7 @@ class SendGlobalEventNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'custom' - def init(self, context): - super(SendGlobalEventNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'Event') diff --git a/blender/arm/logicnode/input/LN_gamepad.py b/blender/arm/logicnode/input/LN_gamepad.py index 09c5cd0e..83e31ac1 100644 --- a/blender/arm/logicnode/input/LN_gamepad.py +++ b/blender/arm/logicnode/input/LN_gamepad.py @@ -46,8 +46,7 @@ class GamepadNode(ArmLogicTreeNode): ('touchpad', 'touchpad', 'touchpad'),], name='', default='cross') - def init(self, context): - super(GamepadNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmBoolSocket', 'State') diff --git a/blender/arm/logicnode/input/LN_gamepad_coords.py b/blender/arm/logicnode/input/LN_gamepad_coords.py index fdb61b75..2648479a 100644 --- a/blender/arm/logicnode/input/LN_gamepad_coords.py +++ b/blender/arm/logicnode/input/LN_gamepad_coords.py @@ -11,8 +11,7 @@ class GamepadCoordsNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'gamepad' - def init(self, context): - super(GamepadCoordsNode, self).init(context) + def arm_init(self, context): self.add_output('ArmVectorSocket', 'Left Stick') self.add_output('ArmVectorSocket', 'Right Stick') self.add_output('ArmVectorSocket', 'Left Movement') diff --git a/blender/arm/logicnode/input/LN_get_cursor_location.py b/blender/arm/logicnode/input/LN_get_cursor_location.py index 2d102343..6e6eedac 100644 --- a/blender/arm/logicnode/input/LN_get_cursor_location.py +++ b/blender/arm/logicnode/input/LN_get_cursor_location.py @@ -7,8 +7,7 @@ class GetCursorLocationNode(ArmLogicTreeNode): arm_section = 'mouse' arm_version = 1 - def init(self, context): - super(GetCursorLocationNode, self).init(context) + def arm_init(self, context): self.add_output('ArmIntSocket', 'X') self.add_output('ArmIntSocket', 'Y') self.add_output('ArmIntSocket', 'Inverted X') diff --git a/blender/arm/logicnode/input/LN_get_cursor_state.py b/blender/arm/logicnode/input/LN_get_cursor_state.py index b09c253a..0d1c7c8f 100644 --- a/blender/arm/logicnode/input/LN_get_cursor_state.py +++ b/blender/arm/logicnode/input/LN_get_cursor_state.py @@ -16,8 +16,7 @@ class GetCursorStateNode(ArmLogicTreeNode): arm_section = 'mouse' arm_version = 1 - def init(self, context): - super(GetCursorStateNode, self).init(context) + def arm_init(self, context): self.outputs.new('ArmBoolSocket', 'Is Hidden Locked') self.outputs.new('ArmBoolSocket', 'Is Hidden') self.outputs.new('ArmBoolSocket', 'Is Locked') diff --git a/blender/arm/logicnode/input/LN_get_mouse_movement.py b/blender/arm/logicnode/input/LN_get_mouse_movement.py index a3ac08d2..fbef5311 100644 --- a/blender/arm/logicnode/input/LN_get_mouse_movement.py +++ b/blender/arm/logicnode/input/LN_get_mouse_movement.py @@ -9,8 +9,7 @@ class GetMouseMovementNode(ArmLogicTreeNode): arm_section = 'mouse' arm_version = 1 - def init(self, context): - super(GetMouseMovementNode, self).init(context) + def arm_init(self, context): self.add_input('ArmFloatSocket', 'X Multiplier', default_value=-1.0) self.add_input('ArmFloatSocket', 'Y Multiplier', default_value=-1.0) diff --git a/blender/arm/logicnode/input/LN_get_touch_location.py b/blender/arm/logicnode/input/LN_get_touch_location.py index 1acf6028..3aea57b6 100644 --- a/blender/arm/logicnode/input/LN_get_touch_location.py +++ b/blender/arm/logicnode/input/LN_get_touch_location.py @@ -7,8 +7,7 @@ class GetTouchLocationNode(ArmLogicTreeNode): arm_section = 'surface' arm_version = 1 - def init(self, context): - super(GetTouchLocationNode, self).init(context) + def arm_init(self, context): self.add_output('ArmIntSocket', 'X') self.add_output('ArmIntSocket', 'Y') self.add_output('ArmIntSocket', 'Inverted X') diff --git a/blender/arm/logicnode/input/LN_get_touch_movement.py b/blender/arm/logicnode/input/LN_get_touch_movement.py index 6c110679..8c37c844 100644 --- a/blender/arm/logicnode/input/LN_get_touch_movement.py +++ b/blender/arm/logicnode/input/LN_get_touch_movement.py @@ -7,8 +7,7 @@ class GetTouchMovementNode(ArmLogicTreeNode): arm_section = 'surface' arm_version = 1 - def init(self, context): - super(GetTouchMovementNode, self).init(context) + def arm_init(self, context): self.add_input('ArmFloatSocket', 'X Multiplier', default_value=-1.0) self.add_input('ArmFloatSocket', 'Y Multiplier', default_value=-1.0) diff --git a/blender/arm/logicnode/input/LN_keyboard.py b/blender/arm/logicnode/input/LN_keyboard.py index b1613b02..7a78e651 100644 --- a/blender/arm/logicnode/input/LN_keyboard.py +++ b/blender/arm/logicnode/input/LN_keyboard.py @@ -70,8 +70,7 @@ class KeyboardNode(ArmLogicTreeNode): ('down', 'down', 'down'),], name='', default='space') - def init(self, context): - super(KeyboardNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmBoolSocket', 'State') diff --git a/blender/arm/logicnode/input/LN_mouse.py b/blender/arm/logicnode/input/LN_mouse.py index 69663544..03624cd7 100644 --- a/blender/arm/logicnode/input/LN_mouse.py +++ b/blender/arm/logicnode/input/LN_mouse.py @@ -21,8 +21,7 @@ class MouseNode(ArmLogicTreeNode): ('right', 'Right', 'Right mouse button')], name='', default='left') - def init(self, context): - super(MouseNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmBoolSocket', 'State') diff --git a/blender/arm/logicnode/input/LN_on_swipe.py b/blender/arm/logicnode/input/LN_on_swipe.py index 61c11913..8bd6226e 100644 --- a/blender/arm/logicnode/input/LN_on_swipe.py +++ b/blender/arm/logicnode/input/LN_on_swipe.py @@ -59,8 +59,7 @@ class OnSwipeNode(ArmLogicTreeNode): super(OnSwipeNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(OnSwipeNode, self).init(context) + def arm_init(self, context): self.inputs.new('ArmFloatSocket', 'Time') self.inputs[-1].default_value = 0.15 self.inputs.new('ArmIntSocket', 'Min Length (px)') diff --git a/blender/arm/logicnode/input/LN_on_tap_screen.py b/blender/arm/logicnode/input/LN_on_tap_screen.py index c0450ca0..61c4a0fa 100644 --- a/blender/arm/logicnode/input/LN_on_tap_screen.py +++ b/blender/arm/logicnode/input/LN_on_tap_screen.py @@ -18,8 +18,7 @@ class OnTapScreen(ArmLogicTreeNode): arm_section = 'Input' arm_version = 1 - def init(self, context): - super(OnTapScreen, self).init(context) + def arm_init(self, context): self.add_input('ArmFloatSocket', 'Duration') self.inputs[-1].default_value = 0.3 self.add_input('ArmFloatSocket', 'Interval') diff --git a/blender/arm/logicnode/input/LN_sensor_coords.py b/blender/arm/logicnode/input/LN_sensor_coords.py index 439f8eb1..a92f38f0 100644 --- a/blender/arm/logicnode/input/LN_sensor_coords.py +++ b/blender/arm/logicnode/input/LN_sensor_coords.py @@ -7,6 +7,5 @@ class SensorCoordsNode(ArmLogicTreeNode): arm_section = 'sensor' arm_version = 1 - def init(self, context): - super(SensorCoordsNode, self).init(context) + def arm_init(self, context): self.add_output('ArmVectorSocket', 'Coords') diff --git a/blender/arm/logicnode/input/LN_set_cursor_state.py b/blender/arm/logicnode/input/LN_set_cursor_state.py index c1f38fde..0c8c2d6e 100644 --- a/blender/arm/logicnode/input/LN_set_cursor_state.py +++ b/blender/arm/logicnode/input/LN_set_cursor_state.py @@ -22,8 +22,7 @@ class SetCursorStateNode(ArmLogicTreeNode): ], name='', default='hide locked') - def init(self, context): - super(SetCursorStateNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmBoolSocket', 'State') diff --git a/blender/arm/logicnode/input/LN_touch.py b/blender/arm/logicnode/input/LN_touch.py index ffbc32c6..f6ff0593 100644 --- a/blender/arm/logicnode/input/LN_touch.py +++ b/blender/arm/logicnode/input/LN_touch.py @@ -15,8 +15,7 @@ class SurfaceNode(ArmLogicTreeNode): ('moved', 'Moved', 'Moved')], name='', default='down') - def init(self, context): - super(SurfaceNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmBoolSocket', 'State') diff --git a/blender/arm/logicnode/input/LN_virtual_button.py b/blender/arm/logicnode/input/LN_virtual_button.py index 28a52189..3850a26e 100644 --- a/blender/arm/logicnode/input/LN_virtual_button.py +++ b/blender/arm/logicnode/input/LN_virtual_button.py @@ -15,8 +15,7 @@ class VirtualButtonNode(ArmLogicTreeNode): name='', default='down') property1: HaxeStringProperty('property1', name='', default='button') - def init(self, context): - super(VirtualButtonNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmBoolSocket', 'State') diff --git a/blender/arm/logicnode/light/LN_set_light_color.py b/blender/arm/logicnode/light/LN_set_light_color.py index 38b3d6ef..5af7cde7 100644 --- a/blender/arm/logicnode/light/LN_set_light_color.py +++ b/blender/arm/logicnode/light/LN_set_light_color.py @@ -6,8 +6,7 @@ class SetLightColorNode(ArmLogicTreeNode): bl_label = 'Set Light Color' arm_version = 1 - def init(self, context): - super(SetLightColorNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Light') self.add_input('ArmColorSocket', 'Color', default_value=[1.0, 1.0, 1.0, 1.0]) diff --git a/blender/arm/logicnode/light/LN_set_light_strength.py b/blender/arm/logicnode/light/LN_set_light_strength.py index cbf11700..bc9fd67e 100644 --- a/blender/arm/logicnode/light/LN_set_light_strength.py +++ b/blender/arm/logicnode/light/LN_set_light_strength.py @@ -6,8 +6,7 @@ class SetLightStrengthNode(ArmLogicTreeNode): bl_label = 'Set Light Strength' arm_version = 1 - def init(self, context): - super(SetLightStrengthNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Light') self.add_input('ArmFloatSocket', 'Strength', default_value=250) diff --git a/blender/arm/logicnode/logic/LN_alternate_output.py b/blender/arm/logicnode/logic/LN_alternate_output.py index 839c75bd..8f0a81e6 100644 --- a/blender/arm/logicnode/logic/LN_alternate_output.py +++ b/blender/arm/logicnode/logic/LN_alternate_output.py @@ -8,8 +8,7 @@ class AlternateNode(ArmLogicTreeNode): arm_section = 'flow' arm_version = 1 - def init(self, context): - super(AlternateNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', '0') diff --git a/blender/arm/logicnode/logic/LN_branch.py b/blender/arm/logicnode/logic/LN_branch.py index d7b5536d..5693f882 100644 --- a/blender/arm/logicnode/logic/LN_branch.py +++ b/blender/arm/logicnode/logic/LN_branch.py @@ -8,8 +8,7 @@ class BranchNode(ArmLogicTreeNode): bl_label = 'Branch' arm_version = 1 - def init(self, context): - super(BranchNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmBoolSocket', 'Bool') diff --git a/blender/arm/logicnode/logic/LN_call_function.py b/blender/arm/logicnode/logic/LN_call_function.py index 20808c71..819407ad 100644 --- a/blender/arm/logicnode/logic/LN_call_function.py +++ b/blender/arm/logicnode/logic/LN_call_function.py @@ -13,8 +13,7 @@ class CallFunctionNode(ArmLogicTreeNode): super(CallFunctionNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(CallFunctionNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmDynamicSocket', 'Trait/Any') self.add_input('ArmStringSocket', 'Function') diff --git a/blender/arm/logicnode/logic/LN_function.py b/blender/arm/logicnode/logic/LN_function.py index 5af1e5e7..2d3fe0dd 100644 --- a/blender/arm/logicnode/logic/LN_function.py +++ b/blender/arm/logicnode/logic/LN_function.py @@ -15,8 +15,7 @@ class FunctionNode(ArmLogicTreeNode): super(FunctionNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(FunctionNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') function_name: StringProperty(name="Name") diff --git a/blender/arm/logicnode/logic/LN_function_output.py b/blender/arm/logicnode/logic/LN_function_output.py index 87343512..073b364f 100644 --- a/blender/arm/logicnode/logic/LN_function_output.py +++ b/blender/arm/logicnode/logic/LN_function_output.py @@ -10,8 +10,7 @@ class FunctionOutputNode(ArmLogicTreeNode): arm_section = 'function' arm_version = 1 - def init(self, context): - super(FunctionOutputNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/logic/LN_gate.py b/blender/arm/logicnode/logic/LN_gate.py index 286c6580..e67b9f23 100644 --- a/blender/arm/logicnode/logic/LN_gate.py +++ b/blender/arm/logicnode/logic/LN_gate.py @@ -38,8 +38,7 @@ class GateNode(ArmLogicTreeNode): super(GateNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(GateNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmDynamicSocket', 'Input 1') self.add_input('ArmDynamicSocket', 'Input 2') diff --git a/blender/arm/logicnode/logic/LN_invert_boolean.py b/blender/arm/logicnode/logic/LN_invert_boolean.py index 5c5475f1..c981c7cc 100644 --- a/blender/arm/logicnode/logic/LN_invert_boolean.py +++ b/blender/arm/logicnode/logic/LN_invert_boolean.py @@ -6,8 +6,7 @@ class NotNode(ArmLogicTreeNode): bl_label = 'Invert Boolean' arm_version = 1 - def init(self, context): - super(NotNode, self).init(context) + def arm_init(self, context): self.add_input('ArmBoolSocket', 'Bool In') self.add_output('ArmBoolSocket', 'Bool Out') diff --git a/blender/arm/logicnode/logic/LN_invert_output.py b/blender/arm/logicnode/logic/LN_invert_output.py index 04c7d596..7a30dbb8 100644 --- a/blender/arm/logicnode/logic/LN_invert_output.py +++ b/blender/arm/logicnode/logic/LN_invert_output.py @@ -8,8 +8,7 @@ class InverseNode(ArmLogicTreeNode): arm_section = 'flow' arm_version = 1 - def init(self, context): - super(InverseNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/logic/LN_is_false.py b/blender/arm/logicnode/logic/LN_is_false.py index a4ec30df..304c025d 100644 --- a/blender/arm/logicnode/logic/LN_is_false.py +++ b/blender/arm/logicnode/logic/LN_is_false.py @@ -10,8 +10,7 @@ class IsFalseNode(ArmLogicTreeNode): bl_label = 'Is False' arm_version = 1 - def init(self, context): - super(IsFalseNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmBoolSocket', 'Bool') diff --git a/blender/arm/logicnode/logic/LN_is_not_null.py b/blender/arm/logicnode/logic/LN_is_not_null.py index 200b4cdf..e18ee5a4 100644 --- a/blender/arm/logicnode/logic/LN_is_not_null.py +++ b/blender/arm/logicnode/logic/LN_is_not_null.py @@ -9,8 +9,7 @@ class IsNotNoneNode(ArmLogicTreeNode): bl_label = 'Is Not Null' arm_version = 1 - def init(self, context): - super(IsNotNoneNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/logic/LN_is_null.py b/blender/arm/logicnode/logic/LN_is_null.py index a08e2c32..da2e120c 100644 --- a/blender/arm/logicnode/logic/LN_is_null.py +++ b/blender/arm/logicnode/logic/LN_is_null.py @@ -10,8 +10,7 @@ class IsNoneNode(ArmLogicTreeNode): bl_label = 'Is Null' arm_version = 1 - def init(self, context): - super(IsNoneNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/logic/LN_is_true.py b/blender/arm/logicnode/logic/LN_is_true.py index 6e88f6b8..17f0b47b 100644 --- a/blender/arm/logicnode/logic/LN_is_true.py +++ b/blender/arm/logicnode/logic/LN_is_true.py @@ -9,8 +9,7 @@ class IsTrueNode(ArmLogicTreeNode): bl_label = 'Is True' arm_version = 1 - def init(self, context): - super(IsTrueNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmBoolSocket', 'Bool') diff --git a/blender/arm/logicnode/logic/LN_loop.py b/blender/arm/logicnode/logic/LN_loop.py index d6ef4b0e..212c514d 100644 --- a/blender/arm/logicnode/logic/LN_loop.py +++ b/blender/arm/logicnode/logic/LN_loop.py @@ -20,8 +20,7 @@ class LoopNode(ArmLogicTreeNode): arm_section = 'flow' arm_version = 1 - def init(self, context): - super(LoopNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmIntSocket', 'From') self.add_input('ArmIntSocket', 'To') diff --git a/blender/arm/logicnode/logic/LN_loop_break.py b/blender/arm/logicnode/logic/LN_loop_break.py index 3c0d8a2c..326d15b8 100644 --- a/blender/arm/logicnode/logic/LN_loop_break.py +++ b/blender/arm/logicnode/logic/LN_loop_break.py @@ -12,6 +12,5 @@ class LoopBreakNode(ArmLogicTreeNode): arm_section = 'flow' arm_version = 1 - def init(self, context): - super(LoopBreakNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') diff --git a/blender/arm/logicnode/logic/LN_merge.py b/blender/arm/logicnode/logic/LN_merge.py index 5c3b8a9f..c5e369de 100644 --- a/blender/arm/logicnode/logic/LN_merge.py +++ b/blender/arm/logicnode/logic/LN_merge.py @@ -14,8 +14,7 @@ class MergeNode(ArmLogicTreeNode): super(MergeNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(MergeNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/logic/LN_null.py b/blender/arm/logicnode/logic/LN_null.py index 7ed6a18f..ed0d9601 100644 --- a/blender/arm/logicnode/logic/LN_null.py +++ b/blender/arm/logicnode/logic/LN_null.py @@ -6,6 +6,5 @@ class NoneNode(ArmLogicTreeNode): bl_label = 'Null' arm_version = 1 - def init(self, context): - super(NoneNode, self).init(context) + def arm_init(self, context): self.add_output('ArmDynamicSocket', 'Null') diff --git a/blender/arm/logicnode/logic/LN_output_sequence.py b/blender/arm/logicnode/logic/LN_output_sequence.py index 3d973cdd..f6166a69 100644 --- a/blender/arm/logicnode/logic/LN_output_sequence.py +++ b/blender/arm/logicnode/logic/LN_output_sequence.py @@ -11,8 +11,7 @@ class SequenceNode(ArmLogicTreeNode): super(SequenceNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(SequenceNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/logic/LN_output_to_boolean.py b/blender/arm/logicnode/logic/LN_output_to_boolean.py index e8b040db..d03c79cf 100644 --- a/blender/arm/logicnode/logic/LN_output_to_boolean.py +++ b/blender/arm/logicnode/logic/LN_output_to_boolean.py @@ -7,8 +7,7 @@ class ToBoolNode(ArmLogicTreeNode): bl_label = 'Output to Boolean' arm_version = 1 - def init(self, context): - super(ToBoolNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmBoolSocket', 'Bool') diff --git a/blender/arm/logicnode/logic/LN_switch_output.py b/blender/arm/logicnode/logic/LN_switch_output.py index 26bd5246..688245e2 100644 --- a/blender/arm/logicnode/logic/LN_switch_output.py +++ b/blender/arm/logicnode/logic/LN_switch_output.py @@ -15,8 +15,7 @@ class SwitchNode(ArmLogicTreeNode): super(SwitchNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(SwitchNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/logic/LN_value_changed.py b/blender/arm/logicnode/logic/LN_value_changed.py index c805d3a1..82af3d5c 100644 --- a/blender/arm/logicnode/logic/LN_value_changed.py +++ b/blender/arm/logicnode/logic/LN_value_changed.py @@ -6,8 +6,7 @@ class ValueChangedNode(ArmLogicTreeNode): bl_label = 'Value Changed' arm_version = 1 - def init(self, context): - super(ValueChangedNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/logic/LN_while_true.py b/blender/arm/logicnode/logic/LN_while_true.py index f817c1fd..b2a7b670 100644 --- a/blender/arm/logicnode/logic/LN_while_true.py +++ b/blender/arm/logicnode/logic/LN_while_true.py @@ -15,8 +15,7 @@ class WhileNode(ArmLogicTreeNode): arm_section = 'flow' arm_version = 1 - def init(self, context): - super(WhileNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmBoolSocket', 'Condition') diff --git a/blender/arm/logicnode/material/LN_get_object_material.py b/blender/arm/logicnode/material/LN_get_object_material.py index ec560fcf..8e64630d 100644 --- a/blender/arm/logicnode/material/LN_get_object_material.py +++ b/blender/arm/logicnode/material/LN_get_object_material.py @@ -6,8 +6,7 @@ class GetMaterialNode(ArmLogicTreeNode): bl_label = 'Get Object Material' arm_version = 1 - def init(self, context): - super(GetMaterialNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmIntSocket', 'Slot') diff --git a/blender/arm/logicnode/material/LN_material.py b/blender/arm/logicnode/material/LN_material.py index 63cf9c46..082f2a80 100644 --- a/blender/arm/logicnode/material/LN_material.py +++ b/blender/arm/logicnode/material/LN_material.py @@ -20,8 +20,7 @@ class MaterialNode(ArmLogicTreeNode): property0: HaxePointerProperty('property0', name='', type=bpy.types.Material) - def init(self, context): - super(MaterialNode, self).init(context) + def arm_init(self, context): self.add_output('ArmDynamicSocket', 'Material', is_var=True) def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/material/LN_set_material_image_param.py b/blender/arm/logicnode/material/LN_set_material_image_param.py index e6188f76..38ae1bf3 100644 --- a/blender/arm/logicnode/material/LN_set_material_image_param.py +++ b/blender/arm/logicnode/material/LN_set_material_image_param.py @@ -7,8 +7,7 @@ class SetMaterialImageParamNode(ArmLogicTreeNode): arm_section = 'params' arm_version = 1 - def init(self, context): - super(SetMaterialImageParamNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmDynamicSocket', 'Material') self.add_input('ArmStringSocket', 'Node') diff --git a/blender/arm/logicnode/material/LN_set_material_rgb_param.py b/blender/arm/logicnode/material/LN_set_material_rgb_param.py index 1b826834..b8a4b5f6 100644 --- a/blender/arm/logicnode/material/LN_set_material_rgb_param.py +++ b/blender/arm/logicnode/material/LN_set_material_rgb_param.py @@ -7,8 +7,7 @@ class SetMaterialRgbParamNode(ArmLogicTreeNode): arm_section = 'params' arm_version = 1 - def init(self, context): - super(SetMaterialRgbParamNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmDynamicSocket', 'Material') self.add_input('ArmStringSocket', 'Node') diff --git a/blender/arm/logicnode/material/LN_set_material_value_param.py b/blender/arm/logicnode/material/LN_set_material_value_param.py index afae0aa7..cabc7b19 100644 --- a/blender/arm/logicnode/material/LN_set_material_value_param.py +++ b/blender/arm/logicnode/material/LN_set_material_value_param.py @@ -7,8 +7,7 @@ class SetMaterialValueParamNode(ArmLogicTreeNode): arm_section = 'params' arm_version = 1 - def init(self, context): - super(SetMaterialValueParamNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmDynamicSocket', 'Material') self.add_input('ArmStringSocket', 'Node') diff --git a/blender/arm/logicnode/material/LN_set_object_material_slot.py b/blender/arm/logicnode/material/LN_set_object_material_slot.py index acfa6a68..ccfab366 100644 --- a/blender/arm/logicnode/material/LN_set_object_material_slot.py +++ b/blender/arm/logicnode/material/LN_set_object_material_slot.py @@ -6,8 +6,7 @@ class SetMaterialSlotNode(ArmLogicTreeNode): bl_label = 'Set Object Material Slot' arm_version = 1 - def init(self, context): - super(SetMaterialSlotNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmDynamicSocket', 'Material') diff --git a/blender/arm/logicnode/math/LN_clamp.py b/blender/arm/logicnode/math/LN_clamp.py index 8337dd97..257c3802 100644 --- a/blender/arm/logicnode/math/LN_clamp.py +++ b/blender/arm/logicnode/math/LN_clamp.py @@ -9,8 +9,7 @@ class ClampNode(ArmLogicTreeNode): bl_label = 'Clamp' arm_version = 1 - def init(self, context): - super(ClampNode, self).init(context) + def arm_init(self, context): self.add_input('ArmFloatSocket', 'Value') self.add_input('ArmFloatSocket', 'Min') self.add_input('ArmFloatSocket', 'Max') diff --git a/blender/arm/logicnode/math/LN_compare.py b/blender/arm/logicnode/math/LN_compare.py index 027ee9ed..c3b86472 100644 --- a/blender/arm/logicnode/math/LN_compare.py +++ b/blender/arm/logicnode/math/LN_compare.py @@ -29,8 +29,7 @@ class CompareNode(ArmLogicTreeNode): super(CompareNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(CompareNode, self).init(context) + def arm_init(self, context): self.add_input('ArmDynamicSocket', 'Value') self.add_input('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/math/LN_deg_to_rad.py b/blender/arm/logicnode/math/LN_deg_to_rad.py index 034e73e9..6ee81e61 100644 --- a/blender/arm/logicnode/math/LN_deg_to_rad.py +++ b/blender/arm/logicnode/math/LN_deg_to_rad.py @@ -7,8 +7,7 @@ class DegToRadNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'angle' - def init(self, context): - super(DegToRadNode, self).init(context) + def arm_init(self, context): self.add_input('ArmFloatSocket', 'Degrees') self.add_output('ArmFloatSocket', 'Radians') diff --git a/blender/arm/logicnode/math/LN_map_range.py b/blender/arm/logicnode/math/LN_map_range.py index 29de5d67..0008ce88 100644 --- a/blender/arm/logicnode/math/LN_map_range.py +++ b/blender/arm/logicnode/math/LN_map_range.py @@ -9,8 +9,7 @@ class MapRangeNode(ArmLogicTreeNode): bl_label = 'Map Range' arm_version = 1 - def init(self, context): - super(MapRangeNode, self).init(context) + def arm_init(self, context): self.add_input('ArmFloatSocket', 'Value', default_value=1.0) self.add_input('ArmFloatSocket', 'From Min') self.add_input('ArmFloatSocket', 'From Max', default_value=1.0) diff --git a/blender/arm/logicnode/math/LN_math.py b/blender/arm/logicnode/math/LN_math.py index ddae6d7f..2271c2dc 100644 --- a/blender/arm/logicnode/math/LN_math.py +++ b/blender/arm/logicnode/math/LN_math.py @@ -98,8 +98,7 @@ class MathNode(ArmLogicTreeNode): def __init__(self): array_nodes[str(id(self))] = self - def init(self, context): - super(MathNode, self).init(context) + def arm_init(self, context): self.add_input('ArmFloatSocket', 'Value 0', default_value=0.0) self.add_input('ArmFloatSocket', 'Value 1', default_value=0.0) diff --git a/blender/arm/logicnode/math/LN_math_expression.py b/blender/arm/logicnode/math/LN_math_expression.py index 4018b3e1..0795d6a8 100644 --- a/blender/arm/logicnode/math/LN_math_expression.py +++ b/blender/arm/logicnode/math/LN_math_expression.py @@ -161,8 +161,7 @@ class MathExpressionNode(ArmLogicTreeNode): def __init__(self): array_nodes[str(id(self))] = self - def init(self, context): - super(MathExpressionNode, self).init(context) + def arm_init(self, context): self.add_input('ArmFloatSocket', self.get_variable_name(0), default_value=0.0) self.add_input('ArmFloatSocket', self.get_variable_name(1), default_value=0.0) self.add_output('ArmFloatSocket', 'Result') diff --git a/blender/arm/logicnode/math/LN_matrix_math.py b/blender/arm/logicnode/math/LN_matrix_math.py index 903a4e87..6a4d200e 100644 --- a/blender/arm/logicnode/math/LN_matrix_math.py +++ b/blender/arm/logicnode/math/LN_matrix_math.py @@ -12,8 +12,7 @@ class MatrixMathNode(ArmLogicTreeNode): items = [('Multiply', 'Multiply', 'Multiply')], name='', default='Multiply') - def init(self, context): - super(MatrixMathNode, self).init(context) + def arm_init(self, context): self.add_input('ArmDynamicSocket', 'Matrix 1') self.add_input('ArmDynamicSocket', 'Matrix 2') diff --git a/blender/arm/logicnode/math/LN_mix.py b/blender/arm/logicnode/math/LN_mix.py index 0d4a98e3..7ae648a4 100644 --- a/blender/arm/logicnode/math/LN_mix.py +++ b/blender/arm/logicnode/math/LN_mix.py @@ -30,8 +30,7 @@ class MixNode(ArmLogicTreeNode): property2: HaxeBoolProperty('property2', name='Clamp', default=False) - def init(self, context): - super(MixNode, self).init(context) + def arm_init(self, context): self.add_input('ArmFloatSocket', 'Factor', default_value=0.0) self.add_input('ArmFloatSocket', 'Value 1', default_value=0.0) self.add_input('ArmFloatSocket', 'Value 2', default_value=1.0) diff --git a/blender/arm/logicnode/math/LN_mix_vector.py b/blender/arm/logicnode/math/LN_mix_vector.py index 9084d75c..4349de86 100644 --- a/blender/arm/logicnode/math/LN_mix_vector.py +++ b/blender/arm/logicnode/math/LN_mix_vector.py @@ -32,8 +32,7 @@ class VectorMixNode(ArmLogicTreeNode): property2: HaxeBoolProperty('property2', name='Clamp', default=False) - def init(self, context): - super(VectorMixNode, self).init(context) + def arm_init(self, context): self.add_input('ArmFloatSocket', 'Factor', default_value=0.0) self.add_input('ArmVectorSocket', 'Vector 1', default_value=[0.0, 0.0, 0.0]) self.add_input('ArmVectorSocket', 'Vector 2', default_value=[1.0, 1.0, 1.0]) diff --git a/blender/arm/logicnode/math/LN_quaternion_math.py b/blender/arm/logicnode/math/LN_quaternion_math.py index 8dfdc590..dfdece72 100644 --- a/blender/arm/logicnode/math/LN_quaternion_math.py +++ b/blender/arm/logicnode/math/LN_quaternion_math.py @@ -155,8 +155,7 @@ class QuaternionMathNode(ArmLogicTreeNode): def __init__(self): array_nodes[str(id(self))] = self - def init(self, context): - super(QuaternionMathNode, self).init(context) + def arm_init(self, context): self.add_input('ArmVectorSocket', 'Quaternion 0', default_value=[0.0, 0.0, 0.0]) self.add_input('ArmVectorSocket', 'Quaternion 1', default_value=[0.0, 0.0, 0.0]) self.add_output('ArmVectorSocket', 'Result') diff --git a/blender/arm/logicnode/math/LN_rad_to_deg.py b/blender/arm/logicnode/math/LN_rad_to_deg.py index 2335acb2..c144ae98 100644 --- a/blender/arm/logicnode/math/LN_rad_to_deg.py +++ b/blender/arm/logicnode/math/LN_rad_to_deg.py @@ -7,8 +7,7 @@ class RadToDegNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'angle' - def init(self, context): - super(RadToDegNode, self).init(context) + def arm_init(self, context): self.add_input('ArmFloatSocket', 'Radians') self.add_output('ArmFloatSocket', 'Degrees') diff --git a/blender/arm/logicnode/math/LN_screen_to_world_space.py b/blender/arm/logicnode/math/LN_screen_to_world_space.py index 0a6c2375..ef1864cb 100644 --- a/blender/arm/logicnode/math/LN_screen_to_world_space.py +++ b/blender/arm/logicnode/math/LN_screen_to_world_space.py @@ -12,8 +12,7 @@ class ScreenToWorldSpaceNode(ArmLogicTreeNode): property0: HaxeBoolProperty('property0', name='Separator Out', default=False) - def init(self, context): - super(ScreenToWorldSpaceNode, self).init(context) + def arm_init(self, context): self.add_input('ArmIntSocket', 'Screen X') self.add_input('ArmIntSocket', 'Screen Y') diff --git a/blender/arm/logicnode/math/LN_separate_quaternion.py b/blender/arm/logicnode/math/LN_separate_quaternion.py index 10d81ded..d8b6f0cf 100644 --- a/blender/arm/logicnode/math/LN_separate_quaternion.py +++ b/blender/arm/logicnode/math/LN_separate_quaternion.py @@ -7,8 +7,7 @@ class SeparateQuaternionNode(ArmLogicTreeNode): arm_section = 'quaternions' arm_version = 1 - def init(self, context): - super(SeparateQuaternionNode, self).init(context) + def arm_init(self, context): self.add_input('ArmVectorSocket', 'Quaternion') self.add_output('ArmFloatSocket', 'X') diff --git a/blender/arm/logicnode/math/LN_separate_rgb.py b/blender/arm/logicnode/math/LN_separate_rgb.py index e76766f1..e2098989 100644 --- a/blender/arm/logicnode/math/LN_separate_rgb.py +++ b/blender/arm/logicnode/math/LN_separate_rgb.py @@ -7,8 +7,7 @@ class SeparateColorNode(ArmLogicTreeNode): arm_section = 'color' arm_version = 1 - def init(self, context): - super(SeparateColorNode, self).init(context) + def arm_init(self, context): self.add_input('ArmColorSocket', 'Color', default_value=[1.0, 1.0, 1.0, 1.0]) self.add_output('ArmFloatSocket', 'R') diff --git a/blender/arm/logicnode/math/LN_separate_xyz.py b/blender/arm/logicnode/math/LN_separate_xyz.py index c2e33ecd..4f7ac735 100644 --- a/blender/arm/logicnode/math/LN_separate_xyz.py +++ b/blender/arm/logicnode/math/LN_separate_xyz.py @@ -7,8 +7,7 @@ class SeparateVectorNode(ArmLogicTreeNode): arm_section = 'vector' arm_version = 1 - def init(self, context): - super(SeparateVectorNode, self).init(context) + def arm_init(self, context): self.add_input('ArmVectorSocket', 'Vector') self.add_output('ArmFloatSocket', 'X') diff --git a/blender/arm/logicnode/math/LN_vector_clamp.py b/blender/arm/logicnode/math/LN_vector_clamp.py index 744880ef..a61e677d 100644 --- a/blender/arm/logicnode/math/LN_vector_clamp.py +++ b/blender/arm/logicnode/math/LN_vector_clamp.py @@ -7,8 +7,7 @@ class VectorClampToSizeNode(ArmLogicTreeNode): arm_section = 'vector' arm_version = 1 - def init(self, context): - super(VectorClampToSizeNode, self).init(context) + def arm_init(self, context): self.add_input('ArmVectorSocket', 'Vector In', default_value=[0.0, 0.0, 0.0]) self.add_input('ArmFloatSocket', 'Min') self.add_input('ArmFloatSocket', 'Max') diff --git a/blender/arm/logicnode/math/LN_vector_math.py b/blender/arm/logicnode/math/LN_vector_math.py index 7654d151..4e9e82a5 100644 --- a/blender/arm/logicnode/math/LN_vector_math.py +++ b/blender/arm/logicnode/math/LN_vector_math.py @@ -123,8 +123,7 @@ class VectorMathNode(ArmLogicTreeNode): def __init__(self): array_nodes[str(id(self))] = self - def init(self, context): - super(VectorMathNode, self).init(context) + def arm_init(self, context): self.add_input('ArmVectorSocket', 'Value 0', default_value=[0.0, 0.0, 0.0]) self.add_input('ArmVectorSocket', 'Value 1', default_value=[0.0, 0.0, 0.0]) diff --git a/blender/arm/logicnode/math/LN_world_to_screen_space.py b/blender/arm/logicnode/math/LN_world_to_screen_space.py index 6b8f519c..b3f16b6a 100644 --- a/blender/arm/logicnode/math/LN_world_to_screen_space.py +++ b/blender/arm/logicnode/math/LN_world_to_screen_space.py @@ -7,8 +7,7 @@ class WorldToScreenSpaceNode(ArmLogicTreeNode): arm_section = 'matrix' arm_version = 1 - def init(self, context): - super(WorldToScreenSpaceNode, self).init(context) + def arm_init(self, context): self.add_input('ArmVectorSocket', 'World') self.add_output('ArmVectorSocket', 'Screen') diff --git a/blender/arm/logicnode/miscellaneous/LN_boolean_to_vector.py b/blender/arm/logicnode/miscellaneous/LN_boolean_to_vector.py index d2999718..5bcb5002 100644 --- a/blender/arm/logicnode/miscellaneous/LN_boolean_to_vector.py +++ b/blender/arm/logicnode/miscellaneous/LN_boolean_to_vector.py @@ -6,8 +6,7 @@ class VectorFromBooleanNode(ArmLogicTreeNode): bl_label = 'Boolean to Vector' arm_version = 1 - def init(self, context): - super(VectorFromBooleanNode, self).init(context) + def arm_init(self, context): self.inputs.new('ArmBoolSocket', 'X') self.inputs.new('ArmBoolSocket', '-X') self.inputs.new('ArmBoolSocket', 'Y') diff --git a/blender/arm/logicnode/miscellaneous/LN_call_group.py b/blender/arm/logicnode/miscellaneous/LN_call_group.py index cb5849bb..87e13ec1 100644 --- a/blender/arm/logicnode/miscellaneous/LN_call_group.py +++ b/blender/arm/logicnode/miscellaneous/LN_call_group.py @@ -17,8 +17,7 @@ class CallGroupNode(ArmLogicTreeNode): property0_: HaxePointerProperty('property0', name='Group', type=bpy.types.NodeTree) - def init(self, context): - super(CallGroupNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/miscellaneous/LN_default_if_null.py b/blender/arm/logicnode/miscellaneous/LN_default_if_null.py index d2bcf730..e1e5f2db 100644 --- a/blender/arm/logicnode/miscellaneous/LN_default_if_null.py +++ b/blender/arm/logicnode/miscellaneous/LN_default_if_null.py @@ -10,8 +10,7 @@ class DefaultIfNullNode(ArmLogicTreeNode): bl_label = 'Default if Null' arm_version = 1 - def init(self, context): - super(DefaultIfNullNode, self).init(context) + def arm_init(self, context): self.inputs.new('ArmDynamicSocket', 'Value In') self.inputs.new('ArmDynamicSocket', 'Default') diff --git a/blender/arm/logicnode/miscellaneous/LN_get_application_time.py b/blender/arm/logicnode/miscellaneous/LN_get_application_time.py index 2e4301d6..ae2ba85a 100644 --- a/blender/arm/logicnode/miscellaneous/LN_get_application_time.py +++ b/blender/arm/logicnode/miscellaneous/LN_get_application_time.py @@ -6,7 +6,6 @@ class TimeNode(ArmLogicTreeNode): bl_label = 'Get Application Time' arm_version = 1 - def init(self, context): - super(TimeNode, self).init(context) + def arm_init(self, context): self.add_output('ArmFloatSocket', 'Time') self.add_output('ArmFloatSocket', 'Delta') diff --git a/blender/arm/logicnode/miscellaneous/LN_get_debug_console_settings.py b/blender/arm/logicnode/miscellaneous/LN_get_debug_console_settings.py index 5bd6653e..e6e04394 100644 --- a/blender/arm/logicnode/miscellaneous/LN_get_debug_console_settings.py +++ b/blender/arm/logicnode/miscellaneous/LN_get_debug_console_settings.py @@ -6,8 +6,7 @@ class GetDebugConsoleSettings(ArmLogicTreeNode): bl_label = 'Get Debug Console Settings' arm_version = 1 - def init(self, context): - super(GetDebugConsoleSettings, self).init(context) + def arm_init(self, context): self.add_output('ArmBoolSocket', 'Visible') self.add_output('ArmFloatSocket', 'Scale') self.add_output('ArmStringSocket', 'Position') diff --git a/blender/arm/logicnode/miscellaneous/LN_get_display_resolution.py b/blender/arm/logicnode/miscellaneous/LN_get_display_resolution.py index 42399587..411e298c 100644 --- a/blender/arm/logicnode/miscellaneous/LN_get_display_resolution.py +++ b/blender/arm/logicnode/miscellaneous/LN_get_display_resolution.py @@ -10,7 +10,6 @@ class DisplayInfoNode(ArmLogicTreeNode): arm_section = 'screen' arm_version = 1 - def init(self, context): - super(DisplayInfoNode, self).init(context) + def arm_init(self, context): self.add_output('ArmIntSocket', 'Width') self.add_output('ArmIntSocket', 'Height') diff --git a/blender/arm/logicnode/miscellaneous/LN_get_fps.py b/blender/arm/logicnode/miscellaneous/LN_get_fps.py index cd693644..4bc298d9 100644 --- a/blender/arm/logicnode/miscellaneous/LN_get_fps.py +++ b/blender/arm/logicnode/miscellaneous/LN_get_fps.py @@ -6,6 +6,5 @@ class GetFPSNode(ArmLogicTreeNode): bl_label = 'Get Frames Per Second' arm_version = 1 - def init(self, context): - super(GetFPSNode, self).init(context) + def arm_init(self, context): self.add_output('ArmIntSocket', 'Count') diff --git a/blender/arm/logicnode/miscellaneous/LN_get_window_resolution.py b/blender/arm/logicnode/miscellaneous/LN_get_window_resolution.py index 1870da26..d2fe52b6 100644 --- a/blender/arm/logicnode/miscellaneous/LN_get_window_resolution.py +++ b/blender/arm/logicnode/miscellaneous/LN_get_window_resolution.py @@ -10,7 +10,6 @@ class WindowInfoNode(ArmLogicTreeNode): arm_section = 'screen' arm_version = 1 - def init(self, context): - super(WindowInfoNode, self).init(context) + def arm_init(self, context): self.add_output('ArmIntSocket', 'Width') self.add_output('ArmIntSocket', 'Height') diff --git a/blender/arm/logicnode/miscellaneous/LN_group_nodes.py b/blender/arm/logicnode/miscellaneous/LN_group_nodes.py index c8d8cdc9..8b08343b 100644 --- a/blender/arm/logicnode/miscellaneous/LN_group_nodes.py +++ b/blender/arm/logicnode/miscellaneous/LN_group_nodes.py @@ -7,6 +7,5 @@ class GroupOutputNode(ArmLogicTreeNode): arm_section = 'group' arm_version = 1 - def init(self, context): - super(GroupOutputNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') diff --git a/blender/arm/logicnode/miscellaneous/LN_set_debug_console_settings.py b/blender/arm/logicnode/miscellaneous/LN_set_debug_console_settings.py index 56d64c89..4bbd4d12 100644 --- a/blender/arm/logicnode/miscellaneous/LN_set_debug_console_settings.py +++ b/blender/arm/logicnode/miscellaneous/LN_set_debug_console_settings.py @@ -13,8 +13,7 @@ class SetDebugConsoleSettings(ArmLogicTreeNode): ('right', 'Anchor Right', 'Anchor the debug console in the top right')], name='', default='right') - def init(self, context): - super(SetDebugConsoleSettings, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmBoolSocket', 'Visible') self.add_input('ArmFloatSocket', 'Scale') diff --git a/blender/arm/logicnode/miscellaneous/LN_set_time_scale.py b/blender/arm/logicnode/miscellaneous/LN_set_time_scale.py index 3cad6073..77bc3f59 100644 --- a/blender/arm/logicnode/miscellaneous/LN_set_time_scale.py +++ b/blender/arm/logicnode/miscellaneous/LN_set_time_scale.py @@ -6,8 +6,7 @@ class SetTimeScaleNode(ArmLogicTreeNode): bl_label = 'Set Time Scale' arm_version = 1 - def init(self, context): - super(SetTimeScaleNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmFloatSocket', 'Scale', default_value=1.0) diff --git a/blender/arm/logicnode/miscellaneous/LN_sleep.py b/blender/arm/logicnode/miscellaneous/LN_sleep.py index 778bd260..884720e7 100644 --- a/blender/arm/logicnode/miscellaneous/LN_sleep.py +++ b/blender/arm/logicnode/miscellaneous/LN_sleep.py @@ -7,8 +7,7 @@ class SleepNode(ArmLogicTreeNode): bl_label = 'Sleep' arm_version = 1 - def init(self, context): - super(SleepNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmFloatSocket', 'Time') diff --git a/blender/arm/logicnode/miscellaneous/LN_timer.py b/blender/arm/logicnode/miscellaneous/LN_timer.py index 0d9e8ce2..e19a36b8 100644 --- a/blender/arm/logicnode/miscellaneous/LN_timer.py +++ b/blender/arm/logicnode/miscellaneous/LN_timer.py @@ -6,8 +6,7 @@ class TimerNode(ArmLogicTreeNode): bl_label = 'Timer' arm_version = 1 - def init(self, context): - super(TimerNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'Start') self.add_input('ArmNodeSocketAction', 'Pause') self.add_input('ArmNodeSocketAction', 'Stop') diff --git a/blender/arm/logicnode/native/LN_call_haxe_static.py b/blender/arm/logicnode/native/LN_call_haxe_static.py index 3b266d22..fd7e2c91 100644 --- a/blender/arm/logicnode/native/LN_call_haxe_static.py +++ b/blender/arm/logicnode/native/LN_call_haxe_static.py @@ -10,8 +10,7 @@ class CallHaxeStaticNode(ArmLogicTreeNode): arm_section = 'haxe' arm_version = 1 - def init(self, context): - super(CallHaxeStaticNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'Function') diff --git a/blender/arm/logicnode/native/LN_detect_mobile_browser.py b/blender/arm/logicnode/native/LN_detect_mobile_browser.py index e2e7afed..4268ab0d 100644 --- a/blender/arm/logicnode/native/LN_detect_mobile_browser.py +++ b/blender/arm/logicnode/native/LN_detect_mobile_browser.py @@ -6,6 +6,5 @@ class DetectMobileBrowserNode(ArmLogicTreeNode): bl_label = 'Detect Mobile Browser' arm_version = 1 - def init(self, context): - super(DetectMobileBrowserNode, self).init(context) + def arm_init(self, context): self.add_output('ArmBoolSocket', 'Mobile') diff --git a/blender/arm/logicnode/native/LN_expression.py b/blender/arm/logicnode/native/LN_expression.py index 092612ed..843869fd 100644 --- a/blender/arm/logicnode/native/LN_expression.py +++ b/blender/arm/logicnode/native/LN_expression.py @@ -11,8 +11,7 @@ class ExpressionNode(ArmLogicTreeNode): property0: HaxeStringProperty('property0', name='', default='') - def init(self, context): - super(ExpressionNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/native/LN_get_haxe_property.py b/blender/arm/logicnode/native/LN_get_haxe_property.py index 71d1db4e..2748a873 100644 --- a/blender/arm/logicnode/native/LN_get_haxe_property.py +++ b/blender/arm/logicnode/native/LN_get_haxe_property.py @@ -9,8 +9,7 @@ class GetHaxePropertyNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'haxe' - def init(self, context): - super(GetHaxePropertyNode, self).init(context) + def arm_init(self, context): self.add_input('ArmDynamicSocket', 'Dynamic') self.add_input('ArmStringSocket', 'Property') diff --git a/blender/arm/logicnode/native/LN_get_system_language.py b/blender/arm/logicnode/native/LN_get_system_language.py index 4c0af5e2..4dcb6cc5 100644 --- a/blender/arm/logicnode/native/LN_get_system_language.py +++ b/blender/arm/logicnode/native/LN_get_system_language.py @@ -7,6 +7,5 @@ class GetSystemLanguage(ArmLogicTreeNode): arm_section = 'Native' arm_version = 1 - def init(self, context): - super(GetSystemLanguage, self).init(context) + def arm_init(self, context): self.add_output('ArmStringSocket', 'Language') diff --git a/blender/arm/logicnode/native/LN_get_system_name.py b/blender/arm/logicnode/native/LN_get_system_name.py index 95f71053..6e08ac3e 100644 --- a/blender/arm/logicnode/native/LN_get_system_name.py +++ b/blender/arm/logicnode/native/LN_get_system_name.py @@ -8,8 +8,7 @@ class GetSystemName(ArmLogicTreeNode): arm_section = 'Native' arm_version = 1 - def init(self, context): - super(GetSystemName, self).init(context) + def arm_init(self, context): self.add_output('ArmStringSocket', 'System Name') self.add_output('ArmBoolSocket', 'Windows') self.add_output('ArmBoolSocket', 'Linux') diff --git a/blender/arm/logicnode/native/LN_loadUrl.py b/blender/arm/logicnode/native/LN_loadUrl.py index ee885e80..fbbed12b 100644 --- a/blender/arm/logicnode/native/LN_loadUrl.py +++ b/blender/arm/logicnode/native/LN_loadUrl.py @@ -6,7 +6,6 @@ class LoadUrlNode(ArmLogicTreeNode): bl_label = 'Load URL' arm_version = 1 - def init(self, context): - super(LoadUrlNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'URL') diff --git a/blender/arm/logicnode/native/LN_print.py b/blender/arm/logicnode/native/LN_print.py index 8c3310a2..8ba303d7 100644 --- a/blender/arm/logicnode/native/LN_print.py +++ b/blender/arm/logicnode/native/LN_print.py @@ -6,8 +6,7 @@ class PrintNode(ArmLogicTreeNode): bl_label = 'Print' arm_version = 1 - def init(self, context): - super(PrintNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'String') diff --git a/blender/arm/logicnode/native/LN_read_file.py b/blender/arm/logicnode/native/LN_read_file.py index a42c269d..dae4805b 100644 --- a/blender/arm/logicnode/native/LN_read_file.py +++ b/blender/arm/logicnode/native/LN_read_file.py @@ -9,8 +9,7 @@ class ReadFileNode(ArmLogicTreeNode): arm_section = 'file' arm_version = 1 - def init(self, context): - super(ReadFileNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'File') self.add_input('ArmBoolSocket', 'Use cache', default_value=1) diff --git a/blender/arm/logicnode/native/LN_read_json.py b/blender/arm/logicnode/native/LN_read_json.py index 7bb6b04e..a48081fd 100644 --- a/blender/arm/logicnode/native/LN_read_json.py +++ b/blender/arm/logicnode/native/LN_read_json.py @@ -9,8 +9,7 @@ class ReadJsonNode(ArmLogicTreeNode): arm_section = 'file' arm_version = 1 - def init(self, context): - super(ReadJsonNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'File') self.add_input('ArmBoolSocket', 'Use cache', default_value=1) diff --git a/blender/arm/logicnode/native/LN_read_storage.py b/blender/arm/logicnode/native/LN_read_storage.py index 144c5bef..d8bd8bd8 100644 --- a/blender/arm/logicnode/native/LN_read_storage.py +++ b/blender/arm/logicnode/native/LN_read_storage.py @@ -9,8 +9,7 @@ class ReadStorageNode(ArmLogicTreeNode): arm_section = 'file' arm_version = 1 - def init(self, context): - super(ReadStorageNode, self).init(context) + def arm_init(self, context): self.add_input('ArmStringSocket', 'Key') self.add_input('ArmStringSocket', 'Default') diff --git a/blender/arm/logicnode/native/LN_script.py b/blender/arm/logicnode/native/LN_script.py index 4d04ddc9..da0fb05d 100644 --- a/blender/arm/logicnode/native/LN_script.py +++ b/blender/arm/logicnode/native/LN_script.py @@ -17,8 +17,7 @@ class ScriptNode(ArmLogicTreeNode): property0_: HaxeStringProperty('property0', name='Text', default='') - def init(self, context): - super(ScriptNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketArray', 'Array') diff --git a/blender/arm/logicnode/native/LN_set_haxe_property.py b/blender/arm/logicnode/native/LN_set_haxe_property.py index 664fbc0a..bba925b3 100644 --- a/blender/arm/logicnode/native/LN_set_haxe_property.py +++ b/blender/arm/logicnode/native/LN_set_haxe_property.py @@ -9,8 +9,7 @@ class SetHaxePropertyNode(ArmLogicTreeNode): arm_section = 'haxe' arm_version = 1 - def init(self, context): - super(SetHaxePropertyNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmDynamicSocket', 'Dynamic') self.add_input('ArmStringSocket', 'Property') diff --git a/blender/arm/logicnode/native/LN_set_vibrate.py b/blender/arm/logicnode/native/LN_set_vibrate.py index bc340e96..17ba464c 100644 --- a/blender/arm/logicnode/native/LN_set_vibrate.py +++ b/blender/arm/logicnode/native/LN_set_vibrate.py @@ -8,8 +8,7 @@ class SetVibrateNode(ArmLogicTreeNode): arm_section = 'Native' arm_version = 1 - def init(self, context): - super(SetVibrateNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmIntSocket', 'Milliseconds') self.inputs[-1].default_value = 100 diff --git a/blender/arm/logicnode/native/LN_shutdown.py b/blender/arm/logicnode/native/LN_shutdown.py index 48b8587f..6292eafa 100644 --- a/blender/arm/logicnode/native/LN_shutdown.py +++ b/blender/arm/logicnode/native/LN_shutdown.py @@ -6,8 +6,7 @@ class ShutdownNode(ArmLogicTreeNode): bl_label = 'Shutdown' arm_version = 1 - def init(self, context): - super(ShutdownNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/native/LN_write_file.py b/blender/arm/logicnode/native/LN_write_file.py index 683cec21..2f5c4996 100644 --- a/blender/arm/logicnode/native/LN_write_file.py +++ b/blender/arm/logicnode/native/LN_write_file.py @@ -9,8 +9,7 @@ class WriteFileNode(ArmLogicTreeNode): arm_section = 'file' arm_version = 1 - def init(self, context): - super(WriteFileNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'File') self.add_input('ArmStringSocket', 'String') diff --git a/blender/arm/logicnode/native/LN_write_json.py b/blender/arm/logicnode/native/LN_write_json.py index 1694e353..3feff3fa 100644 --- a/blender/arm/logicnode/native/LN_write_json.py +++ b/blender/arm/logicnode/native/LN_write_json.py @@ -9,8 +9,7 @@ class WriteJsonNode(ArmLogicTreeNode): arm_section = 'file' arm_version = 1 - def init(self, context): - super(WriteJsonNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'File') self.add_input('ArmDynamicSocket', 'Dynamic') diff --git a/blender/arm/logicnode/native/LN_write_storage.py b/blender/arm/logicnode/native/LN_write_storage.py index bc9a58b0..727cbca9 100644 --- a/blender/arm/logicnode/native/LN_write_storage.py +++ b/blender/arm/logicnode/native/LN_write_storage.py @@ -9,8 +9,7 @@ class WriteStorageNode(ArmLogicTreeNode): arm_section = 'file' arm_version = 1 - def init(self, context): - super(WriteStorageNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'Key') self.add_input('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/navmesh/LN_go_to_location.py b/blender/arm/logicnode/navmesh/LN_go_to_location.py index be97f2eb..7285c2cf 100644 --- a/blender/arm/logicnode/navmesh/LN_go_to_location.py +++ b/blender/arm/logicnode/navmesh/LN_go_to_location.py @@ -6,8 +6,7 @@ class GoToLocationNode(ArmLogicTreeNode): bl_label = 'Go to Location' arm_version = 1 - def init(self, context): - super(GoToLocationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmDynamicSocket', 'Location') diff --git a/blender/arm/logicnode/navmesh/LN_navigable_location.py b/blender/arm/logicnode/navmesh/LN_navigable_location.py index f6850bbc..3e8b8f8b 100644 --- a/blender/arm/logicnode/navmesh/LN_navigable_location.py +++ b/blender/arm/logicnode/navmesh/LN_navigable_location.py @@ -6,6 +6,5 @@ class NavigableLocationNode(ArmLogicTreeNode): bl_label = 'Navigable Location' arm_version = 1 - def init(self, context): - super(NavigableLocationNode, self).init(context) + def arm_init(self, context): self.add_output('ArmDynamicSocket', 'Location') diff --git a/blender/arm/logicnode/navmesh/LN_pick_navmesh_location.py b/blender/arm/logicnode/navmesh/LN_pick_navmesh_location.py index 063174c7..2ae40119 100644 --- a/blender/arm/logicnode/navmesh/LN_pick_navmesh_location.py +++ b/blender/arm/logicnode/navmesh/LN_pick_navmesh_location.py @@ -6,8 +6,7 @@ class PickLocationNode(ArmLogicTreeNode): bl_label = 'Pick NavMesh Location' arm_version = 1 - def init(self, context): - super(PickLocationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'NavMesh') self.add_input('ArmVectorSocket', 'Screen Coords') diff --git a/blender/arm/logicnode/navmesh/LN_stop_agent.py b/blender/arm/logicnode/navmesh/LN_stop_agent.py index d7740fcf..ff49fc9f 100644 --- a/blender/arm/logicnode/navmesh/LN_stop_agent.py +++ b/blender/arm/logicnode/navmesh/LN_stop_agent.py @@ -6,8 +6,7 @@ class StopAgentNode(ArmLogicTreeNode): bl_label = 'Stop Agent' arm_version = 1 - def init(self, context): - super(StopAgentNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') diff --git a/blender/arm/logicnode/object/LN_get_distance.py b/blender/arm/logicnode/object/LN_get_distance.py index 13b6a5be..5885e890 100644 --- a/blender/arm/logicnode/object/LN_get_distance.py +++ b/blender/arm/logicnode/object/LN_get_distance.py @@ -9,8 +9,7 @@ class GetDistanceNode(ArmLogicTreeNode): bl_label = 'Get Distance' arm_version = 1 - def init(self, context): - super(GetDistanceNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmNodeSocketObject', 'Object') diff --git a/blender/arm/logicnode/object/LN_get_object_by_name.py b/blender/arm/logicnode/object/LN_get_object_by_name.py index 92c7220d..a19bdf06 100644 --- a/blender/arm/logicnode/object/LN_get_object_by_name.py +++ b/blender/arm/logicnode/object/LN_get_object_by_name.py @@ -5,13 +5,12 @@ from arm.logicnode.arm_nodes import * class GetObjectNode(ArmLogicTreeNode): """Searches for a object that uses the given name in the current active scene and returns it.""" - + bl_idname = 'LNGetObjectNode' bl_label = 'Get Object by Name' arm_version = 1 - def init(self, context): - super(GetObjectNode, self).init(context) + def arm_init(self, context): self.add_input('ArmStringSocket', 'Name') self.add_output('ArmNodeSocketObject', 'Object') diff --git a/blender/arm/logicnode/object/LN_get_object_child.py b/blender/arm/logicnode/object/LN_get_object_child.py index 8ff69a55..76409006 100644 --- a/blender/arm/logicnode/object/LN_get_object_child.py +++ b/blender/arm/logicnode/object/LN_get_object_child.py @@ -16,8 +16,7 @@ class GetChildNode(ArmLogicTreeNode): ], name='', default='By Name') - def init(self, context): - super(GetChildNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Parent') self.add_input('ArmStringSocket', 'Child Name') diff --git a/blender/arm/logicnode/object/LN_get_object_children.py b/blender/arm/logicnode/object/LN_get_object_children.py index 2ccbcecb..a23bf33f 100644 --- a/blender/arm/logicnode/object/LN_get_object_children.py +++ b/blender/arm/logicnode/object/LN_get_object_children.py @@ -7,8 +7,7 @@ class GetChildrenNode(ArmLogicTreeNode): arm_section = 'relations' arm_version = 1 - def init(self, context): - super(GetChildrenNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Parent') self.add_output('ArmNodeSocketArray', 'Children') diff --git a/blender/arm/logicnode/object/LN_get_object_mesh.py b/blender/arm/logicnode/object/LN_get_object_mesh.py index 83d45e35..9339134e 100644 --- a/blender/arm/logicnode/object/LN_get_object_mesh.py +++ b/blender/arm/logicnode/object/LN_get_object_mesh.py @@ -7,8 +7,7 @@ class GetMeshNode(ArmLogicTreeNode): arm_section = 'props' arm_version = 1 - def init(self, context): - super(GetMeshNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') self.add_output('ArmDynamicSocket', 'Mesh') diff --git a/blender/arm/logicnode/object/LN_get_object_name.py b/blender/arm/logicnode/object/LN_get_object_name.py index e6938510..bb242d32 100644 --- a/blender/arm/logicnode/object/LN_get_object_name.py +++ b/blender/arm/logicnode/object/LN_get_object_name.py @@ -7,8 +7,7 @@ class GetNameNode(ArmLogicTreeNode): arm_section = 'props' arm_version = 1 - def init(self, context): - super(GetNameNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') self.add_output('ArmStringSocket', 'Name') diff --git a/blender/arm/logicnode/object/LN_get_object_offscreen.py b/blender/arm/logicnode/object/LN_get_object_offscreen.py index 1eb38bd8..6bd85f8b 100644 --- a/blender/arm/logicnode/object/LN_get_object_offscreen.py +++ b/blender/arm/logicnode/object/LN_get_object_offscreen.py @@ -7,8 +7,7 @@ class GetObjectOffscreenNode(ArmLogicTreeNode): arm_section = 'props' arm_version = 1 - def init(self, context): - super(GetObjectOffscreenNode, self).init(context) + def arm_init(self, context): self.inputs.new('ArmNodeSocketObject', 'Object') self.outputs.new('ArmBoolSocket', 'Is Object Offscreen') diff --git a/blender/arm/logicnode/object/LN_get_object_parent.py b/blender/arm/logicnode/object/LN_get_object_parent.py index d856d008..5ca239a6 100644 --- a/blender/arm/logicnode/object/LN_get_object_parent.py +++ b/blender/arm/logicnode/object/LN_get_object_parent.py @@ -9,8 +9,7 @@ class GetParentNode(ArmLogicTreeNode): arm_section = 'relations' arm_version = 1 - def init(self, context): - super(GetParentNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Child') self.add_output('ArmNodeSocketObject', 'Parent') diff --git a/blender/arm/logicnode/object/LN_get_object_property.py b/blender/arm/logicnode/object/LN_get_object_property.py index ecd62857..3332677b 100644 --- a/blender/arm/logicnode/object/LN_get_object_property.py +++ b/blender/arm/logicnode/object/LN_get_object_property.py @@ -9,8 +9,7 @@ class GetPropertyNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'props' - def init(self, context): - super(GetPropertyNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmStringSocket', 'Property') diff --git a/blender/arm/logicnode/object/LN_get_object_visible.py b/blender/arm/logicnode/object/LN_get_object_visible.py index 0f3e1b1c..e01cbf57 100644 --- a/blender/arm/logicnode/object/LN_get_object_visible.py +++ b/blender/arm/logicnode/object/LN_get_object_visible.py @@ -10,8 +10,7 @@ class GetVisibleNode(ArmLogicTreeNode): arm_section = 'props' arm_version = 1 - def init(self, context): - super(GetVisibleNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') self.add_output('ArmBoolSocket', 'Is Object Visible') diff --git a/blender/arm/logicnode/object/LN_mesh.py b/blender/arm/logicnode/object/LN_mesh.py index cefcca05..3f05dda2 100644 --- a/blender/arm/logicnode/object/LN_mesh.py +++ b/blender/arm/logicnode/object/LN_mesh.py @@ -11,8 +11,7 @@ class MeshNode(ArmLogicTreeNode): property0_get: HaxePointerProperty('property0_get', name='', type=bpy.types.Mesh) - def init(self, context): - super(MeshNode, self).init(context) + def arm_init(self, context): self.add_output('ArmDynamicSocket', 'Mesh', is_var=True) def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/object/LN_object.py b/blender/arm/logicnode/object/LN_object.py index 3992bb40..8b9b0636 100644 --- a/blender/arm/logicnode/object/LN_object.py +++ b/blender/arm/logicnode/object/LN_object.py @@ -6,8 +6,7 @@ class ObjectNode(ArmLogicTreeNode): bl_label = 'Object' arm_version = 1 - def init(self, context): - super(ObjectNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object In') self.add_output('ArmNodeSocketObject', 'Object Out', is_var=True) diff --git a/blender/arm/logicnode/object/LN_remove_object.py b/blender/arm/logicnode/object/LN_remove_object.py index 7783da82..78d0f956 100644 --- a/blender/arm/logicnode/object/LN_remove_object.py +++ b/blender/arm/logicnode/object/LN_remove_object.py @@ -6,8 +6,7 @@ class RemoveObjectNode(ArmLogicTreeNode): bl_label = 'Remove Object' arm_version = 1 - def init(self, context): - super(RemoveObjectNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') diff --git a/blender/arm/logicnode/object/LN_remove_object_parent.py b/blender/arm/logicnode/object/LN_remove_object_parent.py index cbfdb84a..80439051 100644 --- a/blender/arm/logicnode/object/LN_remove_object_parent.py +++ b/blender/arm/logicnode/object/LN_remove_object_parent.py @@ -7,8 +7,7 @@ class ClearParentNode(ArmLogicTreeNode): arm_section = 'relations' arm_version = 1 - def init(self, context): - super(ClearParentNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmBoolSocket', 'Keep Transform', default_value=True) diff --git a/blender/arm/logicnode/object/LN_self_object.py b/blender/arm/logicnode/object/LN_self_object.py index aa4021ca..0dcbd0d0 100644 --- a/blender/arm/logicnode/object/LN_self_object.py +++ b/blender/arm/logicnode/object/LN_self_object.py @@ -6,6 +6,5 @@ class SelfObjectNode(ArmLogicTreeNode): bl_label = 'Self Object' arm_version = 1 - def init(self, context): - super(SelfObjectNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketObject', 'Object') diff --git a/blender/arm/logicnode/object/LN_set_object_mesh.py b/blender/arm/logicnode/object/LN_set_object_mesh.py index 601f36ae..114e0078 100644 --- a/blender/arm/logicnode/object/LN_set_object_mesh.py +++ b/blender/arm/logicnode/object/LN_set_object_mesh.py @@ -7,8 +7,7 @@ class SetMeshNode(ArmLogicTreeNode): arm_section = 'props' arm_version = 1 - def init(self, context): - super(SetMeshNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmDynamicSocket', 'Mesh') diff --git a/blender/arm/logicnode/object/LN_set_object_name.py b/blender/arm/logicnode/object/LN_set_object_name.py index 05f20794..236e25b6 100644 --- a/blender/arm/logicnode/object/LN_set_object_name.py +++ b/blender/arm/logicnode/object/LN_set_object_name.py @@ -7,8 +7,7 @@ class SetNameNode(ArmLogicTreeNode): arm_section = 'props' arm_version = 1 - def init(self, context): - super(SetNameNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmStringSocket', 'Name') diff --git a/blender/arm/logicnode/object/LN_set_object_parent.py b/blender/arm/logicnode/object/LN_set_object_parent.py index 30b50d1a..c5e425ee 100644 --- a/blender/arm/logicnode/object/LN_set_object_parent.py +++ b/blender/arm/logicnode/object/LN_set_object_parent.py @@ -9,8 +9,7 @@ class SetParentNode(ArmLogicTreeNode): arm_section = 'relations' arm_version = 1 - def init(self, context): - super(SetParentNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmNodeSocketObject', 'Parent', default_value='Parent') diff --git a/blender/arm/logicnode/object/LN_set_object_property.py b/blender/arm/logicnode/object/LN_set_object_property.py index b41494fa..326beb2f 100644 --- a/blender/arm/logicnode/object/LN_set_object_property.py +++ b/blender/arm/logicnode/object/LN_set_object_property.py @@ -14,8 +14,7 @@ class SetPropertyNode(ArmLogicTreeNode): arm_section = 'props' arm_version = 1 - def init(self, context): - super(SetPropertyNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmStringSocket', 'Property') diff --git a/blender/arm/logicnode/object/LN_set_object_visible.py b/blender/arm/logicnode/object/LN_set_object_visible.py index 53e354a6..09107e9e 100644 --- a/blender/arm/logicnode/object/LN_set_object_visible.py +++ b/blender/arm/logicnode/object/LN_set_object_visible.py @@ -17,8 +17,7 @@ class SetVisibleNode(ArmLogicTreeNode): ], name='', default='object') - def init(self, context): - super(SetVisibleNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmBoolSocket', 'Visible') diff --git a/blender/arm/logicnode/object/LN_spawn_object.py b/blender/arm/logicnode/object/LN_spawn_object.py index 49097d86..de4ff22f 100644 --- a/blender/arm/logicnode/object/LN_spawn_object.py +++ b/blender/arm/logicnode/object/LN_spawn_object.py @@ -2,13 +2,12 @@ from arm.logicnode.arm_nodes import * class SpawnObjectNode(ArmLogicTreeNode): """Spawns the given object if present in the current active scene. The spawned object has the same name of its instance, but they are treated as different objects.""" - + bl_idname = 'LNSpawnObjectNode' bl_label = 'Spawn Object' arm_version = 1 - def init(self, context): - super(SpawnObjectNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmDynamicSocket', 'Transform') diff --git a/blender/arm/logicnode/object/LN_spawn_object_by_name.py b/blender/arm/logicnode/object/LN_spawn_object_by_name.py index bd629b21..3fed2d76 100644 --- a/blender/arm/logicnode/object/LN_spawn_object_by_name.py +++ b/blender/arm/logicnode/object/LN_spawn_object_by_name.py @@ -11,8 +11,7 @@ class SpawnObjectByNameNode(ArmLogicTreeNode): type=bpy.types.Scene, name='Scene', description='The scene from which to take the object') - def init(self, context): - super(SpawnObjectByNameNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'Name') self.add_input('ArmDynamicSocket', 'Transform') diff --git a/blender/arm/logicnode/physics/LN_Add_rigid_body.py b/blender/arm/logicnode/physics/LN_Add_rigid_body.py index 7376c096..97fa8e80 100644 --- a/blender/arm/logicnode/physics/LN_Add_rigid_body.py +++ b/blender/arm/logicnode/physics/LN_Add_rigid_body.py @@ -78,8 +78,7 @@ class AddRigidBodyNode(ArmLogicTreeNode): ('Mesh', 'Mesh', 'Mesh')], name='Shape', default='Box') - def init(self, context): - super(AddRigidBodyNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') diff --git a/blender/arm/logicnode/physics/LN_add_physics_constraint.py b/blender/arm/logicnode/physics/LN_add_physics_constraint.py index 89c2d1e0..192fd804 100644 --- a/blender/arm/logicnode/physics/LN_add_physics_constraint.py +++ b/blender/arm/logicnode/physics/LN_add_physics_constraint.py @@ -137,8 +137,7 @@ class AddPhysicsConstraintNode(ArmLogicTreeNode): def __init__(self): array_nodes[str(id(self))] = self - def init(self, context): - super(AddPhysicsConstraintNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Pivot Object') self.add_input('ArmNodeSocketObject', 'RB 1') diff --git a/blender/arm/logicnode/physics/LN_apply_force.py b/blender/arm/logicnode/physics/LN_apply_force.py index bfe29b07..20aba1b1 100644 --- a/blender/arm/logicnode/physics/LN_apply_force.py +++ b/blender/arm/logicnode/physics/LN_apply_force.py @@ -16,8 +16,7 @@ class ApplyForceNode(ArmLogicTreeNode): arm_section = 'force' arm_version = 1 - def init(self, context): - super(ApplyForceNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'RB') self.add_input('ArmVectorSocket', 'Force') diff --git a/blender/arm/logicnode/physics/LN_apply_force_at_location.py b/blender/arm/logicnode/physics/LN_apply_force_at_location.py index 7a9de48e..145627f4 100644 --- a/blender/arm/logicnode/physics/LN_apply_force_at_location.py +++ b/blender/arm/logicnode/physics/LN_apply_force_at_location.py @@ -19,8 +19,7 @@ class ApplyForceAtLocationNode(ArmLogicTreeNode): arm_section = 'force' arm_version = 1 - def init(self, context): - super(ApplyForceAtLocationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'RB') self.add_input('ArmVectorSocket', 'Force') diff --git a/blender/arm/logicnode/physics/LN_apply_impulse.py b/blender/arm/logicnode/physics/LN_apply_impulse.py index ad41b870..68c2006d 100644 --- a/blender/arm/logicnode/physics/LN_apply_impulse.py +++ b/blender/arm/logicnode/physics/LN_apply_impulse.py @@ -16,8 +16,7 @@ class ApplyImpulseNode(ArmLogicTreeNode): arm_section = 'force' arm_version = 1 - def init(self, context): - super(ApplyImpulseNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'RB') self.add_input('ArmVectorSocket', 'Impulse') diff --git a/blender/arm/logicnode/physics/LN_apply_impulse_at_location.py b/blender/arm/logicnode/physics/LN_apply_impulse_at_location.py index 54a9c0ef..24d55a6d 100644 --- a/blender/arm/logicnode/physics/LN_apply_impulse_at_location.py +++ b/blender/arm/logicnode/physics/LN_apply_impulse_at_location.py @@ -19,8 +19,7 @@ class ApplyImpulseAtLocationNode(ArmLogicTreeNode): arm_section = 'force' arm_version = 1 - def init(self, context): - super(ApplyImpulseAtLocationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'RB') self.add_input('ArmVectorSocket', 'Impulse') diff --git a/blender/arm/logicnode/physics/LN_apply_torque.py b/blender/arm/logicnode/physics/LN_apply_torque.py index 7e00d966..70d2633e 100644 --- a/blender/arm/logicnode/physics/LN_apply_torque.py +++ b/blender/arm/logicnode/physics/LN_apply_torque.py @@ -7,8 +7,7 @@ class ApplyTorqueNode(ArmLogicTreeNode): arm_section = 'force' arm_version = 1 - def init(self, context): - super(ApplyTorqueNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'RB') self.add_input('ArmVectorSocket', 'Torque') diff --git a/blender/arm/logicnode/physics/LN_apply_torque_impulse.py b/blender/arm/logicnode/physics/LN_apply_torque_impulse.py index 91662926..2bf208c2 100644 --- a/blender/arm/logicnode/physics/LN_apply_torque_impulse.py +++ b/blender/arm/logicnode/physics/LN_apply_torque_impulse.py @@ -7,8 +7,7 @@ class ApplyTorqueImpulseNode(ArmLogicTreeNode): arm_section = 'force' arm_version = 1 - def init(self, context): - super(ApplyTorqueImpulseNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'RB') self.add_input('ArmVectorSocket', 'Torque') diff --git a/blender/arm/logicnode/physics/LN_get_rb_contacts.py b/blender/arm/logicnode/physics/LN_get_rb_contacts.py index fd1cf788..9f63fae5 100644 --- a/blender/arm/logicnode/physics/LN_get_rb_contacts.py +++ b/blender/arm/logicnode/physics/LN_get_rb_contacts.py @@ -11,8 +11,7 @@ class GetContactsNode(ArmLogicTreeNode): arm_section = 'contact' arm_version = 1 - def init(self, context): - super(GetContactsNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'RB') self.add_output('ArmNodeSocketArray', 'Contacts') diff --git a/blender/arm/logicnode/physics/LN_get_rb_first_contact.py b/blender/arm/logicnode/physics/LN_get_rb_first_contact.py index 9781c184..ea8ea7af 100644 --- a/blender/arm/logicnode/physics/LN_get_rb_first_contact.py +++ b/blender/arm/logicnode/physics/LN_get_rb_first_contact.py @@ -10,8 +10,7 @@ class GetFirstContactNode(ArmLogicTreeNode): arm_section = 'contact' arm_version = 1 - def init(self, context): - super(GetFirstContactNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'RB') self.add_output('ArmNodeSocketObject', 'First Contact') diff --git a/blender/arm/logicnode/physics/LN_get_rb_velocity.py b/blender/arm/logicnode/physics/LN_get_rb_velocity.py index 33269ccb..18dcadeb 100644 --- a/blender/arm/logicnode/physics/LN_get_rb_velocity.py +++ b/blender/arm/logicnode/physics/LN_get_rb_velocity.py @@ -6,8 +6,7 @@ class GetVelocityNode(ArmLogicTreeNode): bl_label = 'Get RB Velocity' arm_version = 1 - def init(self, context): - super(GetVelocityNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'RB') self.add_input('ArmBoolSocket', 'Linear On Local Axis') self.add_input('ArmBoolSocket', 'Angular On Local Axis') diff --git a/blender/arm/logicnode/physics/LN_get_world_gravity.py b/blender/arm/logicnode/physics/LN_get_world_gravity.py index fe810eec..0fb4aec8 100644 --- a/blender/arm/logicnode/physics/LN_get_world_gravity.py +++ b/blender/arm/logicnode/physics/LN_get_world_gravity.py @@ -9,6 +9,5 @@ class GetGravityNode(ArmLogicTreeNode): bl_label = 'Get World Gravity' arm_version = 1 - def init(self, context): - super(GetGravityNode, self).init(context) + def arm_init(self, context): self.add_output('ArmVectorSocket', 'World Gravity') diff --git a/blender/arm/logicnode/physics/LN_has_contact.py b/blender/arm/logicnode/physics/LN_has_contact.py index 4bd2151f..627180e3 100644 --- a/blender/arm/logicnode/physics/LN_has_contact.py +++ b/blender/arm/logicnode/physics/LN_has_contact.py @@ -7,8 +7,7 @@ class HasContactNode(ArmLogicTreeNode): arm_section = 'contact' arm_version = 1 - def init(self, context): - super(HasContactNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'RB 1') self.add_input('ArmNodeSocketObject', 'RB 2') diff --git a/blender/arm/logicnode/physics/LN_has_contact_array.py b/blender/arm/logicnode/physics/LN_has_contact_array.py index e64d342e..a7a6b759 100644 --- a/blender/arm/logicnode/physics/LN_has_contact_array.py +++ b/blender/arm/logicnode/physics/LN_has_contact_array.py @@ -7,8 +7,7 @@ class HasContactArrayNode(ArmLogicTreeNode): arm_section = 'contact' arm_version = 1 - def init(self, context): - super(HasContactArrayNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'RB') self.add_input('ArmNodeSocketArray', 'RBs') diff --git a/blender/arm/logicnode/physics/LN_on_contact.py b/blender/arm/logicnode/physics/LN_on_contact.py index a96feb01..8ca26aab 100644 --- a/blender/arm/logicnode/physics/LN_on_contact.py +++ b/blender/arm/logicnode/physics/LN_on_contact.py @@ -23,8 +23,7 @@ class OnContactNode(ArmLogicTreeNode): ('end', 'End', 'The contact between the rigid bodies ends')], name='', default='begin') - def init(self, context): - super(OnContactNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'RB 1') self.add_input('ArmNodeSocketObject', 'RB 2') diff --git a/blender/arm/logicnode/physics/LN_on_contact_array.py b/blender/arm/logicnode/physics/LN_on_contact_array.py index 9a35acf1..c39a6ad2 100644 --- a/blender/arm/logicnode/physics/LN_on_contact_array.py +++ b/blender/arm/logicnode/physics/LN_on_contact_array.py @@ -14,8 +14,7 @@ class OnContactArrayNode(ArmLogicTreeNode): ('end', 'End', 'The contact between the rigid bodies ends')], name='', default='begin') - def init(self, context): - super(OnContactArrayNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'RB') self.add_input('ArmNodeSocketArray', 'RBs') diff --git a/blender/arm/logicnode/physics/LN_on_volume_trigger.py b/blender/arm/logicnode/physics/LN_on_volume_trigger.py index 614a7e8f..0c4cf2d5 100644 --- a/blender/arm/logicnode/physics/LN_on_volume_trigger.py +++ b/blender/arm/logicnode/physics/LN_on_volume_trigger.py @@ -17,8 +17,7 @@ class OnVolumeTriggerNode(ArmLogicTreeNode): ('end', 'End', 'The contact between the rigid bodies ends')], name='', default='begin') - def init(self, context): - super(OnVolumeTriggerNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object 1') self.add_input('ArmNodeSocketObject', 'Object 2') diff --git a/blender/arm/logicnode/physics/LN_physics_constraint.py b/blender/arm/logicnode/physics/LN_physics_constraint.py index d4d1c539..f8aafce9 100644 --- a/blender/arm/logicnode/physics/LN_physics_constraint.py +++ b/blender/arm/logicnode/physics/LN_physics_constraint.py @@ -51,8 +51,7 @@ class PhysicsConstraintNode(ArmLogicTreeNode): def __init__(self): array_nodes[str(id(self))] = self - def init(self, context): - super(PhysicsConstraintNode, self).init(context) + def arm_init(self, context): self.add_input('ArmFloatSocket', 'Lower limit') self.add_input('ArmFloatSocket', 'Upper limit') self.add_output('ArmDynamicSocket', 'Constraint') diff --git a/blender/arm/logicnode/physics/LN_pick_rb.py b/blender/arm/logicnode/physics/LN_pick_rb.py index 65e34f54..25d926b0 100644 --- a/blender/arm/logicnode/physics/LN_pick_rb.py +++ b/blender/arm/logicnode/physics/LN_pick_rb.py @@ -3,14 +3,14 @@ from arm.logicnode.arm_nodes import * class PickObjectNode(ArmLogicTreeNode): """Picks the rigid body in the given location using the screen coordinates (2D). - + @seeNode Mask - - @input Screen Coords: the location at which to pick, in screen + + @input Screen Coords: the location at which to pick, in screen coordinates @input Mask: a bit mask value to specify which objects are considered - + @output RB: the object that was hit @output Hit: the hit position in world coordinates """ @@ -19,8 +19,7 @@ class PickObjectNode(ArmLogicTreeNode): arm_section = 'ray' arm_version = 1 - def init(self, context): - super(PickObjectNode, self).init(context) + def arm_init(self, context): self.add_input('ArmVectorSocket', 'Screen Coords') self.add_input('ArmIntSocket', 'Mask', default_value=1) diff --git a/blender/arm/logicnode/physics/LN_ray_cast.py b/blender/arm/logicnode/physics/LN_ray_cast.py index 79995333..1fa4401a 100644 --- a/blender/arm/logicnode/physics/LN_ray_cast.py +++ b/blender/arm/logicnode/physics/LN_ray_cast.py @@ -22,8 +22,7 @@ class RayCastNode(ArmLogicTreeNode): arm_section = 'ray' arm_version = 1 - def init(self, context): - super(RayCastNode, self).init(context) + def arm_init(self, context): self.add_input('ArmVectorSocket', 'From') self.add_input('ArmVectorSocket', 'To') self.add_input('ArmIntSocket', 'Mask', default_value=1) diff --git a/blender/arm/logicnode/physics/LN_remove_rb.py b/blender/arm/logicnode/physics/LN_remove_rb.py index 158493ae..7c05c68e 100644 --- a/blender/arm/logicnode/physics/LN_remove_rb.py +++ b/blender/arm/logicnode/physics/LN_remove_rb.py @@ -6,8 +6,7 @@ class RemovePhysicsNode (ArmLogicTreeNode): bl_label = 'Remove RB' arm_version = 1 - def init(self, context): - super(RemovePhysicsNode, self).init(context) + def arm_init(self, context): self.inputs.new('ArmNodeSocketAction', 'In') self.inputs.new('ArmNodeSocketObject', 'RB') diff --git a/blender/arm/logicnode/physics/LN_set_rb_activation_state.py b/blender/arm/logicnode/physics/LN_set_rb_activation_state.py index 120ead99..63eaec4c 100644 --- a/blender/arm/logicnode/physics/LN_set_rb_activation_state.py +++ b/blender/arm/logicnode/physics/LN_set_rb_activation_state.py @@ -16,8 +16,7 @@ class SetActivationStateNode(ArmLogicTreeNode): ], name='', default='inactive') - def init(self, context): - super(SetActivationStateNode, self).init(context) + def arm_init(self, context): self.inputs.new('ArmNodeSocketAction', 'In') self.inputs.new('ArmNodeSocketObject', 'RB') diff --git a/blender/arm/logicnode/physics/LN_set_rb_friction.py b/blender/arm/logicnode/physics/LN_set_rb_friction.py index c2267732..7efdcd79 100644 --- a/blender/arm/logicnode/physics/LN_set_rb_friction.py +++ b/blender/arm/logicnode/physics/LN_set_rb_friction.py @@ -7,8 +7,7 @@ class SetFrictionNode (ArmLogicTreeNode): bl_icon = 'NONE' arm_version = 1 - def init(self, context): - super(SetFrictionNode, self).init(context) + def arm_init(self, context): self.inputs.new('ArmNodeSocketAction', 'In') self.inputs.new('ArmNodeSocketObject', 'RB') self.inputs.new('ArmFloatSocket', 'Friction') diff --git a/blender/arm/logicnode/physics/LN_set_rb_gravity_enabled.py b/blender/arm/logicnode/physics/LN_set_rb_gravity_enabled.py index 73f0405d..27a576a1 100644 --- a/blender/arm/logicnode/physics/LN_set_rb_gravity_enabled.py +++ b/blender/arm/logicnode/physics/LN_set_rb_gravity_enabled.py @@ -6,8 +6,7 @@ class SetGravityEnabledNode(ArmLogicTreeNode): bl_label = 'Set RB Gravity Enabled' arm_version = 1 - def init(self, context): - super(SetGravityEnabledNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'RB') self.add_input('ArmBoolSocket', 'Enabled') diff --git a/blender/arm/logicnode/physics/LN_set_rb_velocity.py b/blender/arm/logicnode/physics/LN_set_rb_velocity.py index 48e95bbc..21980ae8 100644 --- a/blender/arm/logicnode/physics/LN_set_rb_velocity.py +++ b/blender/arm/logicnode/physics/LN_set_rb_velocity.py @@ -6,8 +6,7 @@ class SetVelocityNode(ArmLogicTreeNode): bl_label = 'Set RB Velocity' arm_version = 1 - def init(self, context): - super(SetVelocityNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'RB') self.add_input('ArmVectorSocket', 'Linear') diff --git a/blender/arm/logicnode/physics/LN_set_world_gravity.py b/blender/arm/logicnode/physics/LN_set_world_gravity.py index e8646994..f8923b29 100644 --- a/blender/arm/logicnode/physics/LN_set_world_gravity.py +++ b/blender/arm/logicnode/physics/LN_set_world_gravity.py @@ -9,8 +9,7 @@ class SetGravityNode(ArmLogicTreeNode): bl_label = 'Set World Gravity' arm_version = 1 - def init(self, context): - super(SetGravityNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmVectorSocket', 'Gravity') diff --git a/blender/arm/logicnode/physics/LN_volume_trigger.py b/blender/arm/logicnode/physics/LN_volume_trigger.py index fab5aa3c..af438c32 100644 --- a/blender/arm/logicnode/physics/LN_volume_trigger.py +++ b/blender/arm/logicnode/physics/LN_volume_trigger.py @@ -19,8 +19,7 @@ class VolumeTriggerNode(ArmLogicTreeNode): ('end', 'End', 'The contact between the rigid bodies ends')], name='', default='begin') - def init(self, context): - super(VolumeTriggerNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object 1') self.add_input('ArmNodeSocketObject', 'Object 2') diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_get_global_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_get_global_node.py index 5cf34dda..f5f61d73 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_get_global_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_get_global_node.py @@ -7,8 +7,7 @@ class ColorgradingGetGlobalNode(ArmLogicTreeNode): arm_section = 'colorgrading' arm_version = 1 - def init(self, context): - super(ColorgradingGetGlobalNode, self).init(context) + def arm_init(self, context): self.add_output('ArmFloatSocket', 'Whitebalance') self.add_output('ArmVectorSocket', 'Tint') self.add_output('ArmVectorSocket', 'Saturation') diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_get_highlight_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_get_highlight_node.py index 583fe71b..c4f16f9f 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_get_highlight_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_get_highlight_node.py @@ -7,8 +7,7 @@ class ColorgradingGetHighlightNode(ArmLogicTreeNode): arm_section = 'colorgrading' arm_version = 1 - def init(self, context): - super(ColorgradingGetHighlightNode, self).init(context) + def arm_init(self, context): self.add_output('ArmFloatSocket', 'HightlightMin') self.add_output('ArmVectorSocket', 'Saturation') self.add_output('ArmVectorSocket', 'Contrast') diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_get_midtone_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_get_midtone_node.py index cd8d860d..29bab2c2 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_get_midtone_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_get_midtone_node.py @@ -7,8 +7,7 @@ class ColorgradingGetMidtoneNode(ArmLogicTreeNode): arm_section = 'colorgrading' arm_version = 1 - def init(self, context): - super(ColorgradingGetMidtoneNode, self).init(context) + def arm_init(self, context): self.add_output('ArmVectorSocket', 'Saturation') self.add_output('ArmVectorSocket', 'Contrast') self.add_output('ArmVectorSocket', 'Gamma') diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_get_shadow_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_get_shadow_node.py index 16c96cb6..06130071 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_get_shadow_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_get_shadow_node.py @@ -7,8 +7,7 @@ class ColorgradingGetShadowNode(ArmLogicTreeNode): arm_section = 'colorgrading' arm_version = 1 - def init(self, context): - super(ColorgradingGetShadowNode, self).init(context) + def arm_init(self, context): self.add_output('ArmFloatSocket', 'ShadowMax') self.add_output('ArmVectorSocket', 'Saturation') self.add_output('ArmVectorSocket', 'Contrast') diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_set_global_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_set_global_node.py index c9258bb6..c4746472 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_set_global_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_set_global_node.py @@ -62,8 +62,7 @@ class ColorgradingSetGlobalNode(ArmLogicTreeNode): def draw_nodes_colorwheel(self, context): pass - def init(self, context): - super(ColorgradingSetGlobalNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') self.draw_nodes_uniform(context) diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_set_highlight_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_set_highlight_node.py index bc775851..f8a801cb 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_set_highlight_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_set_highlight_node.py @@ -60,8 +60,7 @@ class ColorgradingSetHighlightNode(ArmLogicTreeNode): def draw_nodes_colorwheel(self, context): pass - def init(self, context): - super(ColorgradingSetHighlightNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') self.draw_nodes_uniform(context) diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_set_midtone_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_set_midtone_node.py index 14dcdbdf..8d8379cb 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_set_midtone_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_set_midtone_node.py @@ -59,8 +59,7 @@ class ColorgradingSetMidtoneNode(ArmLogicTreeNode): def draw_nodes_colorwheel(self, context): pass - def init(self, context): - super(ColorgradingSetMidtoneNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') self.draw_nodes_uniform(context) diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_set_shadow_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_set_shadow_node.py index a3abd236..84fa8956 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_set_shadow_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_set_shadow_node.py @@ -60,8 +60,7 @@ class ColorgradingSetShadowNode(ArmLogicTreeNode): def draw_nodes_colorwheel(self, context): pass - def init(self, context): - super(ColorgradingSetShadowNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') self.draw_nodes_uniform(context) diff --git a/blender/arm/logicnode/postprocess/LN_get_bloom_settings.py b/blender/arm/logicnode/postprocess/LN_get_bloom_settings.py index 744e7721..4dd36c42 100644 --- a/blender/arm/logicnode/postprocess/LN_get_bloom_settings.py +++ b/blender/arm/logicnode/postprocess/LN_get_bloom_settings.py @@ -6,8 +6,7 @@ class BloomGetNode(ArmLogicTreeNode): bl_label = 'Get Bloom Settings' arm_version = 1 - def init(self, context): - super(BloomGetNode, self).init(context) + def arm_init(self, context): self.add_output('ArmFloatSocket', 'Threshold') self.add_output('ArmFloatSocket', 'Strength') self.add_output('ArmFloatSocket', 'Radius') diff --git a/blender/arm/logicnode/postprocess/LN_get_ca_settings.py b/blender/arm/logicnode/postprocess/LN_get_ca_settings.py index 51fb624d..f4064fb4 100644 --- a/blender/arm/logicnode/postprocess/LN_get_ca_settings.py +++ b/blender/arm/logicnode/postprocess/LN_get_ca_settings.py @@ -6,7 +6,6 @@ class ChromaticAberrationGetNode(ArmLogicTreeNode): bl_label = 'Get CA Settings' arm_version = 1 - def init(self, context): - super(ChromaticAberrationGetNode, self).init(context) + def arm_init(self, context): self.add_output('ArmFloatSocket', 'Strength') self.add_output('ArmFloatSocket', 'Samples') diff --git a/blender/arm/logicnode/postprocess/LN_get_camera_post_process.py b/blender/arm/logicnode/postprocess/LN_get_camera_post_process.py index 71906eae..786cee06 100644 --- a/blender/arm/logicnode/postprocess/LN_get_camera_post_process.py +++ b/blender/arm/logicnode/postprocess/LN_get_camera_post_process.py @@ -6,8 +6,7 @@ class CameraGetNode(ArmLogicTreeNode): bl_label = 'Get Camera Post Process' arm_version = 1 - def init(self, context): - super(CameraGetNode, self).init(context) + def arm_init(self, context): self.add_output('ArmFloatSocket', 'F-Stop') self.add_output('ArmFloatSocket', 'Shutter Time') self.add_output('ArmFloatSocket', 'ISO') diff --git a/blender/arm/logicnode/postprocess/LN_get_lenstexture_settings.py b/blender/arm/logicnode/postprocess/LN_get_lenstexture_settings.py index fd93f516..8ffa9a3d 100644 --- a/blender/arm/logicnode/postprocess/LN_get_lenstexture_settings.py +++ b/blender/arm/logicnode/postprocess/LN_get_lenstexture_settings.py @@ -6,8 +6,7 @@ class LenstextureGetNode(ArmLogicTreeNode): bl_label = 'Get Lenstexture Settings' arm_version = 1 - def init(self, context): - super(LenstextureGetNode, self).init(context) + def arm_init(self, context): self.add_output('ArmFloatSocket', 'Center Min Clip') self.add_output('ArmFloatSocket', 'Center Max Clip') self.add_output('ArmFloatSocket', 'Luminance Min') diff --git a/blender/arm/logicnode/postprocess/LN_get_ssao_settings.py b/blender/arm/logicnode/postprocess/LN_get_ssao_settings.py index 7208d123..d263ae60 100644 --- a/blender/arm/logicnode/postprocess/LN_get_ssao_settings.py +++ b/blender/arm/logicnode/postprocess/LN_get_ssao_settings.py @@ -6,8 +6,7 @@ class SSAOGetNode(ArmLogicTreeNode): bl_label = 'Get SSAO Settings' arm_version = 1 - def init(self, context): - super(SSAOGetNode, self).init(context) + def arm_init(self, context): self.add_output('ArmFloatSocket', 'Radius') self.add_output('ArmFloatSocket', 'Strength') self.add_output('ArmFloatSocket', 'Max Steps') diff --git a/blender/arm/logicnode/postprocess/LN_get_ssr_settings.py b/blender/arm/logicnode/postprocess/LN_get_ssr_settings.py index 69ac4405..684685c9 100644 --- a/blender/arm/logicnode/postprocess/LN_get_ssr_settings.py +++ b/blender/arm/logicnode/postprocess/LN_get_ssr_settings.py @@ -6,8 +6,7 @@ class SSRGetNode(ArmLogicTreeNode): bl_label = 'Get SSR Settings' arm_version = 1 - def init(self, context): - super(SSRGetNode, self).init(context) + def arm_init(self, context): self.add_output('ArmFloatSocket', 'SSR Step') self.add_output('ArmFloatSocket', 'SSR Step Min') self.add_output('ArmFloatSocket', 'SSR Search') diff --git a/blender/arm/logicnode/postprocess/LN_lenstexture_set.py b/blender/arm/logicnode/postprocess/LN_lenstexture_set.py index 4ee7380f..53698ccd 100644 --- a/blender/arm/logicnode/postprocess/LN_lenstexture_set.py +++ b/blender/arm/logicnode/postprocess/LN_lenstexture_set.py @@ -6,8 +6,7 @@ class LenstextureSetNode(ArmLogicTreeNode): bl_label = 'Set Lenstexture' arm_version = 1 - def init(self, context): - super(LenstextureSetNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmFloatSocket', 'Center Min Clip', default_value=0.1) self.add_input('ArmFloatSocket', 'Center Max Clip', default_value=0.5) diff --git a/blender/arm/logicnode/postprocess/LN_set_bloom_settings.py b/blender/arm/logicnode/postprocess/LN_set_bloom_settings.py index 90c8d5a3..7c463480 100644 --- a/blender/arm/logicnode/postprocess/LN_set_bloom_settings.py +++ b/blender/arm/logicnode/postprocess/LN_set_bloom_settings.py @@ -6,8 +6,7 @@ class BloomSetNode(ArmLogicTreeNode): bl_label = 'Set Bloom Settings' arm_version = 1 - def init(self, context): - super(BloomSetNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmFloatSocket', 'Threshold', default_value=1.00) self.add_input('ArmFloatSocket', 'Strength', default_value=3.50) diff --git a/blender/arm/logicnode/postprocess/LN_set_ca_settings.py b/blender/arm/logicnode/postprocess/LN_set_ca_settings.py index be459419..5031cd0a 100644 --- a/blender/arm/logicnode/postprocess/LN_set_ca_settings.py +++ b/blender/arm/logicnode/postprocess/LN_set_ca_settings.py @@ -6,8 +6,7 @@ class ChromaticAberrationSetNode(ArmLogicTreeNode): bl_label = 'Set CA Settings' arm_version = 1 - def init(self, context): - super(ChromaticAberrationSetNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmFloatSocket', 'Strength', default_value=2.0) self.add_input('ArmIntSocket', 'Samples', default_value=32) diff --git a/blender/arm/logicnode/postprocess/LN_set_camera_post_process.py b/blender/arm/logicnode/postprocess/LN_set_camera_post_process.py index e589b6c9..e6a59bc4 100644 --- a/blender/arm/logicnode/postprocess/LN_set_camera_post_process.py +++ b/blender/arm/logicnode/postprocess/LN_set_camera_post_process.py @@ -6,8 +6,7 @@ class CameraSetNode(ArmLogicTreeNode): bl_label = 'Set Camera Post Process' arm_version = 1 - def init(self, context): - super(CameraSetNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmFloatSocket', 'F-stop', default_value=2.0) self.add_input('ArmFloatSocket', 'Shutter Time', default_value=1.0) diff --git a/blender/arm/logicnode/postprocess/LN_set_ssao_settings.py b/blender/arm/logicnode/postprocess/LN_set_ssao_settings.py index 386e980b..e401b955 100644 --- a/blender/arm/logicnode/postprocess/LN_set_ssao_settings.py +++ b/blender/arm/logicnode/postprocess/LN_set_ssao_settings.py @@ -6,8 +6,7 @@ class SSAOSetNode(ArmLogicTreeNode): bl_label = 'Set SSAO Settings' arm_version = 1 - def init(self, context): - super(SSAOSetNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmFloatSocket', 'Radius', default_value=1.0) self.add_input('ArmFloatSocket', 'Strength', default_value=5.0) diff --git a/blender/arm/logicnode/postprocess/LN_set_ssr_settings.py b/blender/arm/logicnode/postprocess/LN_set_ssr_settings.py index fd6bb111..9e3da739 100644 --- a/blender/arm/logicnode/postprocess/LN_set_ssr_settings.py +++ b/blender/arm/logicnode/postprocess/LN_set_ssr_settings.py @@ -6,8 +6,7 @@ class SSRSetNode(ArmLogicTreeNode): bl_label = 'Set SSR Settings' arm_version = 1 - def init(self, context): - super(SSRSetNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmFloatSocket', 'SSR Step', default_value=0.04) self.add_input('ArmFloatSocket', 'SSR Step Min', default_value=0.05) diff --git a/blender/arm/logicnode/random/LN_random_boolean.py b/blender/arm/logicnode/random/LN_random_boolean.py index 039a2c91..1a2f46b3 100644 --- a/blender/arm/logicnode/random/LN_random_boolean.py +++ b/blender/arm/logicnode/random/LN_random_boolean.py @@ -7,6 +7,5 @@ class RandomBooleanNode(ArmLogicTreeNode): bl_label = 'Random Boolean' arm_version = 1 - def init(self, context): - super(RandomBooleanNode, self).init(context) + def arm_init(self, context): self.add_output('ArmBoolSocket', 'Bool') diff --git a/blender/arm/logicnode/random/LN_random_choice.py b/blender/arm/logicnode/random/LN_random_choice.py index ee273513..7ba4749a 100644 --- a/blender/arm/logicnode/random/LN_random_choice.py +++ b/blender/arm/logicnode/random/LN_random_choice.py @@ -7,8 +7,7 @@ class RandomChoiceNode(ArmLogicTreeNode): bl_label = 'Random Choice' arm_version = 1 - def init(self, context): - super().init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketArray', 'Array') self.add_output('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/random/LN_random_color.py b/blender/arm/logicnode/random/LN_random_color.py index 38c36442..9d142b15 100644 --- a/blender/arm/logicnode/random/LN_random_color.py +++ b/blender/arm/logicnode/random/LN_random_color.py @@ -7,6 +7,5 @@ class RandomColorNode(ArmLogicTreeNode): bl_label = 'Random Color' arm_version = 1 - def init(self, context): - super(RandomColorNode, self).init(context) + def arm_init(self, context): self.add_output('ArmColorSocket', 'Color') diff --git a/blender/arm/logicnode/random/LN_random_float.py b/blender/arm/logicnode/random/LN_random_float.py index 22dca241..7bb08dde 100644 --- a/blender/arm/logicnode/random/LN_random_float.py +++ b/blender/arm/logicnode/random/LN_random_float.py @@ -7,8 +7,7 @@ class RandomFloatNode(ArmLogicTreeNode): bl_label = 'Random Float' arm_version = 1 - def init(self, context): - super(RandomFloatNode, self).init(context) + def arm_init(self, context): self.add_input('ArmFloatSocket', 'Min') self.add_input('ArmFloatSocket', 'Max', default_value=1.0) # self.add_input('ArmIntSocket', 'Seed') diff --git a/blender/arm/logicnode/random/LN_random_integer.py b/blender/arm/logicnode/random/LN_random_integer.py index 0b69d5a7..166a2f0a 100644 --- a/blender/arm/logicnode/random/LN_random_integer.py +++ b/blender/arm/logicnode/random/LN_random_integer.py @@ -7,8 +7,7 @@ class RandomIntegerNode(ArmLogicTreeNode): bl_label = 'Random Integer' arm_version = 1 - def init(self, context): - super(RandomIntegerNode, self).init(context) + def arm_init(self, context): self.add_input('ArmIntSocket', 'Min') self.add_input('ArmIntSocket', 'Max', default_value=2) self.add_output('ArmIntSocket', 'Int') diff --git a/blender/arm/logicnode/random/LN_random_output.py b/blender/arm/logicnode/random/LN_random_output.py index fc41b35d..72f249f3 100644 --- a/blender/arm/logicnode/random/LN_random_output.py +++ b/blender/arm/logicnode/random/LN_random_output.py @@ -11,8 +11,7 @@ class RandomOutputNode(ArmLogicTreeNode): def __init__(self): array_nodes[str(id(self))] = self - def init(self, context): - super().init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') diff --git a/blender/arm/logicnode/random/LN_random_vector.py b/blender/arm/logicnode/random/LN_random_vector.py index 11161f8e..b49beabe 100644 --- a/blender/arm/logicnode/random/LN_random_vector.py +++ b/blender/arm/logicnode/random/LN_random_vector.py @@ -7,8 +7,7 @@ class RandomVectorNode(ArmLogicTreeNode): bl_label = 'Random Vector' arm_version = 1 - def init(self, context): - super(RandomVectorNode, self).init(context) + def arm_init(self, context): self.add_input('ArmVectorSocket', 'Min', default_value=[-1.0, -1.0, -1.0]) self.add_input('ArmVectorSocket', 'Max', default_value=[1.0, 1.0, 1.0]) self.add_output('ArmVectorSocket', 'Vector') diff --git a/blender/arm/logicnode/renderpath/LN_set_msaa_quality.py b/blender/arm/logicnode/renderpath/LN_set_msaa_quality.py index 0578c2d8..81511a03 100644 --- a/blender/arm/logicnode/renderpath/LN_set_msaa_quality.py +++ b/blender/arm/logicnode/renderpath/LN_set_msaa_quality.py @@ -15,8 +15,7 @@ class RpMSAANode(ArmLogicTreeNode): ], name='', default='1') - def init(self, context): - super(RpMSAANode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/renderpath/LN_set_post_process_quality.py b/blender/arm/logicnode/renderpath/LN_set_post_process_quality.py index a2d01c1b..faa883bb 100644 --- a/blender/arm/logicnode/renderpath/LN_set_post_process_quality.py +++ b/blender/arm/logicnode/renderpath/LN_set_post_process_quality.py @@ -15,8 +15,7 @@ class RpConfigNode(ArmLogicTreeNode): ], name='', default='SSGI') - def init(self, context): - super(RpConfigNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmBoolSocket', 'Enable') diff --git a/blender/arm/logicnode/renderpath/LN_set_shader_uniform.py b/blender/arm/logicnode/renderpath/LN_set_shader_uniform.py index 7de5ccfe..8afbcd75 100644 --- a/blender/arm/logicnode/renderpath/LN_set_shader_uniform.py +++ b/blender/arm/logicnode/renderpath/LN_set_shader_uniform.py @@ -33,8 +33,7 @@ class SetShaderUniformNode(ArmLogicTreeNode): description="The type of the uniform", update=on_update_uniform_type) - def init(self, context): - super().init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'Uniform Name') self.add_input('ArmFloatSocket', 'Float') diff --git a/blender/arm/logicnode/renderpath/LN_set_shadows_quality.py b/blender/arm/logicnode/renderpath/LN_set_shadows_quality.py index 0d26e950..be16096e 100644 --- a/blender/arm/logicnode/renderpath/LN_set_shadows_quality.py +++ b/blender/arm/logicnode/renderpath/LN_set_shadows_quality.py @@ -13,8 +13,7 @@ class RpShadowQualityNode(ArmLogicTreeNode): ], name='', default='Medium') - def init(self, context): - super(RpShadowQualityNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/renderpath/LN_set_ssaa_quality.py b/blender/arm/logicnode/renderpath/LN_set_ssaa_quality.py index 375b76e2..7608749a 100644 --- a/blender/arm/logicnode/renderpath/LN_set_ssaa_quality.py +++ b/blender/arm/logicnode/renderpath/LN_set_ssaa_quality.py @@ -14,8 +14,7 @@ class RpSuperSampleNode(ArmLogicTreeNode): ], name='', default='1') - def init(self, context): - super(RpSuperSampleNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/scene/LN_collection.py b/blender/arm/logicnode/scene/LN_collection.py index ce4e952c..95d9c5ad 100644 --- a/blender/arm/logicnode/scene/LN_collection.py +++ b/blender/arm/logicnode/scene/LN_collection.py @@ -14,8 +14,7 @@ class GroupNode(ArmLogicTreeNode): property0: HaxePointerProperty('property0', name='', type=bpy.types.Collection) - def init(self, context): - super(GroupNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketArray', 'Array') def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/scene/LN_create_collection.py b/blender/arm/logicnode/scene/LN_create_collection.py index 5e6157c3..94e98a01 100644 --- a/blender/arm/logicnode/scene/LN_create_collection.py +++ b/blender/arm/logicnode/scene/LN_create_collection.py @@ -7,8 +7,7 @@ class CreateCollectionNode(ArmLogicTreeNode): arm_section = 'collection' arm_version = 1 - def init(self, context): - super(CreateCollectionNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'Collection') diff --git a/blender/arm/logicnode/scene/LN_get_collection.py b/blender/arm/logicnode/scene/LN_get_collection.py index 2048650e..74a0f05d 100644 --- a/blender/arm/logicnode/scene/LN_get_collection.py +++ b/blender/arm/logicnode/scene/LN_get_collection.py @@ -10,8 +10,7 @@ class GetGroupNode(ArmLogicTreeNode): arm_section = 'collection' arm_version = 1 - def init(self, context): - super(GetGroupNode, self).init(context) + def arm_init(self, context): self.add_input('ArmStringSocket', 'Name') self.add_output('ArmNodeSocketArray', 'Objects') diff --git a/blender/arm/logicnode/scene/LN_get_scene_active.py b/blender/arm/logicnode/scene/LN_get_scene_active.py index 841a3fe1..f99c72c0 100644 --- a/blender/arm/logicnode/scene/LN_get_scene_active.py +++ b/blender/arm/logicnode/scene/LN_get_scene_active.py @@ -6,6 +6,5 @@ class ActiveSceneNode(ArmLogicTreeNode): bl_label = 'Get Scene Active' arm_version = 1 - def init(self, context): - super(ActiveSceneNode, self).init(context) + def arm_init(self, context): self.add_output('ArmDynamicSocket', 'Scene') diff --git a/blender/arm/logicnode/scene/LN_get_scene_root.py b/blender/arm/logicnode/scene/LN_get_scene_root.py index 658cbfa2..2dc4e428 100644 --- a/blender/arm/logicnode/scene/LN_get_scene_root.py +++ b/blender/arm/logicnode/scene/LN_get_scene_root.py @@ -6,6 +6,5 @@ class SceneRootNode(ArmLogicTreeNode): bl_label = 'Get Scene Root' arm_version = 1 - def init(self, context): - super(SceneRootNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketObject', 'Object') diff --git a/blender/arm/logicnode/scene/LN_global_object.py b/blender/arm/logicnode/scene/LN_global_object.py index 015da8ca..ca001743 100644 --- a/blender/arm/logicnode/scene/LN_global_object.py +++ b/blender/arm/logicnode/scene/LN_global_object.py @@ -7,6 +7,5 @@ class GlobalObjectNode(ArmLogicTreeNode): bl_label = 'Global Object' arm_version = 1 - def init(self, context): - super(GlobalObjectNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketObject', 'Object') diff --git a/blender/arm/logicnode/scene/LN_remove_collection.py b/blender/arm/logicnode/scene/LN_remove_collection.py index 3e6d49c5..813c13d6 100644 --- a/blender/arm/logicnode/scene/LN_remove_collection.py +++ b/blender/arm/logicnode/scene/LN_remove_collection.py @@ -7,8 +7,7 @@ class RemoveGroupNode(ArmLogicTreeNode): arm_section = 'collection' arm_version = 1 - def init(self, context): - super(RemoveGroupNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmStringSocket', 'Collection') diff --git a/blender/arm/logicnode/scene/LN_remove_scene_active.py b/blender/arm/logicnode/scene/LN_remove_scene_active.py index 815b941a..4fd2ac66 100644 --- a/blender/arm/logicnode/scene/LN_remove_scene_active.py +++ b/blender/arm/logicnode/scene/LN_remove_scene_active.py @@ -6,8 +6,7 @@ class RemoveActiveSceneNode(ArmLogicTreeNode): bl_label = 'Remove Scene Active' arm_version = 1 - def init(self, context): - super(RemoveActiveSceneNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/scene/LN_scene.py b/blender/arm/logicnode/scene/LN_scene.py index d2ed0b60..c4849838 100644 --- a/blender/arm/logicnode/scene/LN_scene.py +++ b/blender/arm/logicnode/scene/LN_scene.py @@ -11,8 +11,7 @@ class SceneNode(ArmLogicTreeNode): property0_get: HaxePointerProperty('property0_get', name='', type=bpy.types.Scene) - def init(self, context): - super(SceneNode, self).init(context) + def arm_init(self, context): self.add_output('ArmDynamicSocket', 'Scene') def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/scene/LN_set_scene_active.py b/blender/arm/logicnode/scene/LN_set_scene_active.py index e839765c..95138c85 100644 --- a/blender/arm/logicnode/scene/LN_set_scene_active.py +++ b/blender/arm/logicnode/scene/LN_set_scene_active.py @@ -6,8 +6,7 @@ class SetSceneNode(ArmLogicTreeNode): bl_label = 'Set Scene Active' arm_version = 1 - def init(self, context): - super(SetSceneNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmDynamicSocket', 'Scene') diff --git a/blender/arm/logicnode/scene/LN_spawn_collection.py b/blender/arm/logicnode/scene/LN_spawn_collection.py index 72c6db7c..c03ed012 100644 --- a/blender/arm/logicnode/scene/LN_spawn_collection.py +++ b/blender/arm/logicnode/scene/LN_spawn_collection.py @@ -30,8 +30,7 @@ class SpawnCollectionNode(ArmLogicTreeNode): property0: HaxePointerProperty('property0', name='Collection', type=bpy.types.Collection) - def init(self, context): - super(SpawnCollectionNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmDynamicSocket', 'Transform') diff --git a/blender/arm/logicnode/scene/LN_spawn_scene.py b/blender/arm/logicnode/scene/LN_spawn_scene.py index 5a2b50dd..e3df2adb 100644 --- a/blender/arm/logicnode/scene/LN_spawn_scene.py +++ b/blender/arm/logicnode/scene/LN_spawn_scene.py @@ -6,8 +6,7 @@ class SpawnSceneNode(ArmLogicTreeNode): bl_label = 'Spawn Scene' arm_version = 1 - def init(self, context): - super(SpawnSceneNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmDynamicSocket', 'Scene') self.add_input('ArmDynamicSocket', 'Transform') diff --git a/blender/arm/logicnode/sound/LN_pause_speaker.py b/blender/arm/logicnode/sound/LN_pause_speaker.py index b211a62c..14fb5852 100644 --- a/blender/arm/logicnode/sound/LN_pause_speaker.py +++ b/blender/arm/logicnode/sound/LN_pause_speaker.py @@ -11,8 +11,7 @@ class PauseSpeakerNode(ArmLogicTreeNode): bl_label = 'Pause Speaker' arm_version = 1 - def init(self, context): - super(PauseSpeakerNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Speaker') diff --git a/blender/arm/logicnode/sound/LN_play_sound.py b/blender/arm/logicnode/sound/LN_play_sound.py index ac72729d..47e622d4 100644 --- a/blender/arm/logicnode/sound/LN_play_sound.py +++ b/blender/arm/logicnode/sound/LN_play_sound.py @@ -60,8 +60,7 @@ class PlaySoundNode(ArmLogicTreeNode): default=False ) - def init(self, context): - super(PlaySoundNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'Play') self.add_input('ArmNodeSocketAction', 'Pause') self.add_input('ArmNodeSocketAction', 'Stop') diff --git a/blender/arm/logicnode/sound/LN_play_speaker.py b/blender/arm/logicnode/sound/LN_play_speaker.py index b91446b9..d4fdeb84 100644 --- a/blender/arm/logicnode/sound/LN_play_speaker.py +++ b/blender/arm/logicnode/sound/LN_play_speaker.py @@ -11,8 +11,7 @@ class PlaySpeakerNode(ArmLogicTreeNode): bl_label = 'Play Speaker' arm_version = 1 - def init(self, context): - super(PlaySpeakerNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Speaker') diff --git a/blender/arm/logicnode/sound/LN_stop_speaker.py b/blender/arm/logicnode/sound/LN_stop_speaker.py index 9b5ba8bc..4940133d 100644 --- a/blender/arm/logicnode/sound/LN_stop_speaker.py +++ b/blender/arm/logicnode/sound/LN_stop_speaker.py @@ -11,8 +11,7 @@ class StopSpeakerNode(ArmLogicTreeNode): bl_label = 'Stop Speaker' arm_version = 1 - def init(self, context): - super(StopSpeakerNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Speaker') diff --git a/blender/arm/logicnode/string/LN_concatenate_string.py b/blender/arm/logicnode/string/LN_concatenate_string.py index 0c8c3e13..3467ddee 100644 --- a/blender/arm/logicnode/string/LN_concatenate_string.py +++ b/blender/arm/logicnode/string/LN_concatenate_string.py @@ -10,8 +10,7 @@ class ConcatenateStringNode(ArmLogicTreeNode): super(ConcatenateStringNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(ConcatenateStringNode, self).init(context) + def arm_init(self, context): self.add_input('ArmStringSocket', 'Input 0') self.add_output('ArmStringSocket', 'String') diff --git a/blender/arm/logicnode/string/LN_parse_float.py b/blender/arm/logicnode/string/LN_parse_float.py index f52fc698..5d14bc34 100644 --- a/blender/arm/logicnode/string/LN_parse_float.py +++ b/blender/arm/logicnode/string/LN_parse_float.py @@ -7,8 +7,7 @@ class ParseFloatNode(ArmLogicTreeNode): arm_section = 'parse' arm_version = 1 - def init(self, context): - super(ParseFloatNode, self).init(context) + def arm_init(self, context): self.add_output('ArmFloatSocket', 'Float') self.add_input('ArmStringSocket', 'String') diff --git a/blender/arm/logicnode/string/LN_split_string.py b/blender/arm/logicnode/string/LN_split_string.py index 79941b81..3b7478aa 100644 --- a/blender/arm/logicnode/string/LN_split_string.py +++ b/blender/arm/logicnode/string/LN_split_string.py @@ -6,8 +6,7 @@ class SplitStringNode(ArmLogicTreeNode): bl_label = 'Split String' arm_version = 1 - def init(self, context): - super(SplitStringNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketArray', 'Array') self.add_input('ArmStringSocket', 'String') diff --git a/blender/arm/logicnode/string/LN_string.py b/blender/arm/logicnode/string/LN_string.py index 6b4679c3..d7f4d6c1 100644 --- a/blender/arm/logicnode/string/LN_string.py +++ b/blender/arm/logicnode/string/LN_string.py @@ -6,8 +6,7 @@ class StringNode(ArmLogicTreeNode): bl_label = 'String' arm_version = 1 - def init(self, context): - super(StringNode, self).init(context) + def arm_init(self, context): self.add_input('ArmStringSocket', 'String In') self.add_output('ArmStringSocket', 'String Out', is_var=True) diff --git a/blender/arm/logicnode/string/LN_string_case.py b/blender/arm/logicnode/string/LN_string_case.py index 195bb6c1..22619e41 100644 --- a/blender/arm/logicnode/string/LN_string_case.py +++ b/blender/arm/logicnode/string/LN_string_case.py @@ -12,8 +12,7 @@ class CaseStringNode(ArmLogicTreeNode): ], name='', default='Upper Case') - def init(self, context): - super(CaseStringNode, self).init(context) + def arm_init(self, context): self.add_input('ArmStringSocket', 'String In') self.add_output('ArmStringSocket', 'String Out') diff --git a/blender/arm/logicnode/string/LN_string_contains.py b/blender/arm/logicnode/string/LN_string_contains.py index aa657ec5..f9811baf 100644 --- a/blender/arm/logicnode/string/LN_string_contains.py +++ b/blender/arm/logicnode/string/LN_string_contains.py @@ -13,8 +13,7 @@ class ContainsStringNode(ArmLogicTreeNode): ], name='', default='Contains') - def init(self, context): - super(ContainsStringNode, self).init(context) + def arm_init(self, context): self.add_input('ArmStringSocket', 'String') self.add_input('ArmStringSocket', 'Find') diff --git a/blender/arm/logicnode/string/LN_string_length.py b/blender/arm/logicnode/string/LN_string_length.py index 9037a1ed..5edf39cb 100644 --- a/blender/arm/logicnode/string/LN_string_length.py +++ b/blender/arm/logicnode/string/LN_string_length.py @@ -6,8 +6,7 @@ class LengthStringNode(ArmLogicTreeNode): bl_label = 'String Length' arm_version = 1 - def init(self, context): - super(LengthStringNode, self).init(context) + def arm_init(self, context): self.add_output('ArmIntSocket', 'Length') self.add_input('ArmStringSocket', 'String') diff --git a/blender/arm/logicnode/string/LN_sub_string.py b/blender/arm/logicnode/string/LN_sub_string.py index b3b0a7c0..647d0e64 100644 --- a/blender/arm/logicnode/string/LN_sub_string.py +++ b/blender/arm/logicnode/string/LN_sub_string.py @@ -6,8 +6,7 @@ class SubStringNode(ArmLogicTreeNode): bl_label = 'Sub String' arm_version = 1 - def init(self, context): - super(SubStringNode, self).init(context) + def arm_init(self, context): self.add_input('ArmStringSocket', 'String In') self.add_input('ArmIntSocket', 'Start') self.add_input('ArmIntSocket', 'End') diff --git a/blender/arm/logicnode/trait/LN_add_trait_to_object.py b/blender/arm/logicnode/trait/LN_add_trait_to_object.py index c2fe8847..aac4a5f3 100644 --- a/blender/arm/logicnode/trait/LN_add_trait_to_object.py +++ b/blender/arm/logicnode/trait/LN_add_trait_to_object.py @@ -6,8 +6,7 @@ class AddTraitNode(ArmLogicTreeNode): bl_label = 'Add Trait to Object' arm_version = 1 - def init(self, context): - super(AddTraitNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmDynamicSocket', 'Trait') diff --git a/blender/arm/logicnode/trait/LN_get_object_trait.py b/blender/arm/logicnode/trait/LN_get_object_trait.py index 7014a2f8..b0890ab2 100644 --- a/blender/arm/logicnode/trait/LN_get_object_trait.py +++ b/blender/arm/logicnode/trait/LN_get_object_trait.py @@ -7,8 +7,7 @@ class GetTraitNode(ArmLogicTreeNode): bl_label = 'Get Object Trait' arm_version = 1 - def init(self, context): - super(GetTraitNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmStringSocket', 'Name') diff --git a/blender/arm/logicnode/trait/LN_get_object_traits.py b/blender/arm/logicnode/trait/LN_get_object_traits.py index ab80f3ce..7fd1dfae 100644 --- a/blender/arm/logicnode/trait/LN_get_object_traits.py +++ b/blender/arm/logicnode/trait/LN_get_object_traits.py @@ -6,8 +6,7 @@ class GetObjectTraitsNode(ArmLogicTreeNode): bl_label = 'Get Object Traits' arm_version = 1 - def init(self, context): - super(GetObjectTraitsNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') self.add_output('ArmNodeSocketArray', 'Traits') diff --git a/blender/arm/logicnode/trait/LN_get_trait_name.py b/blender/arm/logicnode/trait/LN_get_trait_name.py index c8156da5..9a34fd70 100644 --- a/blender/arm/logicnode/trait/LN_get_trait_name.py +++ b/blender/arm/logicnode/trait/LN_get_trait_name.py @@ -6,8 +6,7 @@ class GetTraitNameNode(ArmLogicTreeNode): bl_label = 'Get Trait Name' arm_version = 1 - def init(self, context): - super(GetTraitNameNode, self).init(context) + def arm_init(self, context): self.add_input('ArmDynamicSocket', 'Trait') self.add_output('ArmStringSocket', 'Name') diff --git a/blender/arm/logicnode/trait/LN_get_trait_paused.py b/blender/arm/logicnode/trait/LN_get_trait_paused.py index 286df85d..12f1ac0d 100644 --- a/blender/arm/logicnode/trait/LN_get_trait_paused.py +++ b/blender/arm/logicnode/trait/LN_get_trait_paused.py @@ -6,8 +6,7 @@ class GetTraitPausedNode(ArmLogicTreeNode): bl_label = 'Get Trait Paused' arm_version = 1 - def init(self, context): - super(GetTraitPausedNode, self).init(context) + def arm_init(self, context): self.add_input('ArmDynamicSocket', 'Trait') self.add_output('ArmBoolSocket', 'Is Paused') diff --git a/blender/arm/logicnode/trait/LN_remove_trait.py b/blender/arm/logicnode/trait/LN_remove_trait.py index 0d40e82a..7e81f12c 100644 --- a/blender/arm/logicnode/trait/LN_remove_trait.py +++ b/blender/arm/logicnode/trait/LN_remove_trait.py @@ -6,8 +6,7 @@ class RemoveTraitNode(ArmLogicTreeNode): bl_label = 'Remove Trait' arm_version = 1 - def init(self, context): - super(RemoveTraitNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmDynamicSocket', 'Trait') diff --git a/blender/arm/logicnode/trait/LN_self_trait.py b/blender/arm/logicnode/trait/LN_self_trait.py index 5e3c85b7..41b09676 100644 --- a/blender/arm/logicnode/trait/LN_self_trait.py +++ b/blender/arm/logicnode/trait/LN_self_trait.py @@ -6,6 +6,5 @@ class SelfTraitNode(ArmLogicTreeNode): bl_label = 'Self Trait' arm_version = 1 - def init(self, context): - super(SelfTraitNode, self).init(context) + def arm_init(self, context): self.add_output('ArmDynamicSocket', 'Trait') diff --git a/blender/arm/logicnode/trait/LN_set_trait_paused.py b/blender/arm/logicnode/trait/LN_set_trait_paused.py index 6c4b595c..739692dd 100644 --- a/blender/arm/logicnode/trait/LN_set_trait_paused.py +++ b/blender/arm/logicnode/trait/LN_set_trait_paused.py @@ -6,8 +6,7 @@ class SetTraitPausedNode(ArmLogicTreeNode): bl_label = 'Set Trait Paused' arm_version = 1 - def init(self, context): - super(SetTraitPausedNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmDynamicSocket', 'Trait') self.add_input('ArmBoolSocket', 'Paused') diff --git a/blender/arm/logicnode/trait/LN_trait.py b/blender/arm/logicnode/trait/LN_trait.py index 4ed2a6dd..bc1b85ef 100644 --- a/blender/arm/logicnode/trait/LN_trait.py +++ b/blender/arm/logicnode/trait/LN_trait.py @@ -10,8 +10,7 @@ class TraitNode(ArmLogicTreeNode): property0: HaxeStringProperty('property0', name='', default='') - def init(self, context): - super(TraitNode, self).init(context) + def arm_init(self, context): self.add_output('ArmDynamicSocket', 'Trait', is_var=True) def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/transform/LN_append_transform.py b/blender/arm/logicnode/transform/LN_append_transform.py index 81a087c0..3a85088e 100644 --- a/blender/arm/logicnode/transform/LN_append_transform.py +++ b/blender/arm/logicnode/transform/LN_append_transform.py @@ -6,8 +6,7 @@ class AppendTransformNode(ArmLogicTreeNode): bl_label = 'Append Transform' arm_version = 1 - def init(self, context): - super(AppendTransformNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmDynamicSocket', 'Transform') diff --git a/blender/arm/logicnode/transform/LN_get_object_location.py b/blender/arm/logicnode/transform/LN_get_object_location.py index 06b15234..f2e3fb58 100644 --- a/blender/arm/logicnode/transform/LN_get_object_location.py +++ b/blender/arm/logicnode/transform/LN_get_object_location.py @@ -7,8 +7,7 @@ class GetLocationNode(ArmLogicTreeNode): arm_section = 'location' arm_version = 1 - def init(self, context): - super(GetLocationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') self.add_output('ArmVectorSocket', 'Location') diff --git a/blender/arm/logicnode/transform/LN_get_object_rotation.py b/blender/arm/logicnode/transform/LN_get_object_rotation.py index 821a47d8..af3ba965 100644 --- a/blender/arm/logicnode/transform/LN_get_object_rotation.py +++ b/blender/arm/logicnode/transform/LN_get_object_rotation.py @@ -7,8 +7,7 @@ class GetRotationNode(ArmLogicTreeNode): arm_section = 'rotation' arm_version = 1 - def init(self, context): - super(GetRotationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') self.add_output('ArmVectorSocket', 'Euler Angles') diff --git a/blender/arm/logicnode/transform/LN_get_object_scale.py b/blender/arm/logicnode/transform/LN_get_object_scale.py index f6f1ff56..67d5d5df 100644 --- a/blender/arm/logicnode/transform/LN_get_object_scale.py +++ b/blender/arm/logicnode/transform/LN_get_object_scale.py @@ -7,8 +7,7 @@ class GetScaleNode(ArmLogicTreeNode): arm_section = 'scale' arm_version = 1 - def init(self, context): - super(GetScaleNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') self.add_output('ArmVectorSocket', 'Scale') diff --git a/blender/arm/logicnode/transform/LN_get_object_transform.py b/blender/arm/logicnode/transform/LN_get_object_transform.py index a10163ae..347dc81b 100644 --- a/blender/arm/logicnode/transform/LN_get_object_transform.py +++ b/blender/arm/logicnode/transform/LN_get_object_transform.py @@ -8,8 +8,7 @@ class GetTransformNode(ArmLogicTreeNode): bl_label = 'Get Object Transform' arm_version = 1 - def init(self, context): - super(GetTransformNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') self.add_output('ArmDynamicSocket', 'Transform') diff --git a/blender/arm/logicnode/transform/LN_get_world_orientation.py b/blender/arm/logicnode/transform/LN_get_world_orientation.py index 28c074c6..9e9b7de4 100644 --- a/blender/arm/logicnode/transform/LN_get_world_orientation.py +++ b/blender/arm/logicnode/transform/LN_get_world_orientation.py @@ -14,8 +14,7 @@ class GetWorldNode(ArmLogicTreeNode): ('Up', 'Up', 'The object up (Z) direction')], name='', default='Look') - def init(self, context): - super(GetWorldNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') self.add_output('ArmVectorSocket', 'Vector') diff --git a/blender/arm/logicnode/transform/LN_look_at.py b/blender/arm/logicnode/transform/LN_look_at.py index a38b20f2..f2bc1bde 100644 --- a/blender/arm/logicnode/transform/LN_look_at.py +++ b/blender/arm/logicnode/transform/LN_look_at.py @@ -17,8 +17,7 @@ class LookAtNode(ArmLogicTreeNode): ('-Z', '-Z', '-Z')], name='With', default='Z') - def init(self, context): - super(LookAtNode, self).init(context) + def arm_init(self, context): self.add_input('ArmVectorSocket', 'From Location') self.add_input('ArmVectorSocket', 'To Location') diff --git a/blender/arm/logicnode/transform/LN_rotate_object.py b/blender/arm/logicnode/transform/LN_rotate_object.py index 773c245f..0e22ede6 100644 --- a/blender/arm/logicnode/transform/LN_rotate_object.py +++ b/blender/arm/logicnode/transform/LN_rotate_object.py @@ -7,8 +7,7 @@ class RotateObjectNode(ArmLogicTreeNode): arm_section = 'rotation' arm_version = 1 - def init(self, context): - super().init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmVectorSocket', 'Euler Angles') diff --git a/blender/arm/logicnode/transform/LN_separate_transform.py b/blender/arm/logicnode/transform/LN_separate_transform.py index 28762a17..66cb1e7f 100644 --- a/blender/arm/logicnode/transform/LN_separate_transform.py +++ b/blender/arm/logicnode/transform/LN_separate_transform.py @@ -6,8 +6,7 @@ class SeparateTransformNode(ArmLogicTreeNode): bl_label = 'Separate Transform' arm_version = 1 - def init(self, context): - super(SeparateTransformNode, self).init(context) + def arm_init(self, context): self.add_input('ArmDynamicSocket', 'Transform') self.add_output('ArmVectorSocket', 'Location') diff --git a/blender/arm/logicnode/transform/LN_set_object_location.py b/blender/arm/logicnode/transform/LN_set_object_location.py index 36a665b5..84473c80 100644 --- a/blender/arm/logicnode/transform/LN_set_object_location.py +++ b/blender/arm/logicnode/transform/LN_set_object_location.py @@ -7,8 +7,7 @@ class SetLocationNode(ArmLogicTreeNode): arm_section = 'location' arm_version = 1 - def init(self, context): - super(SetLocationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmVectorSocket', 'Location') diff --git a/blender/arm/logicnode/transform/LN_set_object_rotation.py b/blender/arm/logicnode/transform/LN_set_object_rotation.py index ac563ff5..6b7efbd9 100644 --- a/blender/arm/logicnode/transform/LN_set_object_rotation.py +++ b/blender/arm/logicnode/transform/LN_set_object_rotation.py @@ -7,8 +7,7 @@ class SetRotationNode(ArmLogicTreeNode): arm_section = 'rotation' arm_version = 1 - def init(self, context): - super(SetRotationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmVectorSocket', 'Euler Angles / Vector XYZ') diff --git a/blender/arm/logicnode/transform/LN_set_object_scale.py b/blender/arm/logicnode/transform/LN_set_object_scale.py index d797cba3..7f7221d9 100644 --- a/blender/arm/logicnode/transform/LN_set_object_scale.py +++ b/blender/arm/logicnode/transform/LN_set_object_scale.py @@ -7,8 +7,7 @@ class SetScaleNode(ArmLogicTreeNode): arm_section = 'scale' arm_version = 1 - def init(self, context): - super(SetScaleNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmVectorSocket', 'Scale', default_value=[1.0, 1.0, 1.0]) diff --git a/blender/arm/logicnode/transform/LN_set_object_transform.py b/blender/arm/logicnode/transform/LN_set_object_transform.py index 99b08ac7..1a139551 100644 --- a/blender/arm/logicnode/transform/LN_set_object_transform.py +++ b/blender/arm/logicnode/transform/LN_set_object_transform.py @@ -6,8 +6,7 @@ class SetTransformNode(ArmLogicTreeNode): bl_label = 'Set Object Transform' arm_version = 1 - def init(self, context): - super(SetTransformNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmDynamicSocket', 'Transform') diff --git a/blender/arm/logicnode/transform/LN_transform.py b/blender/arm/logicnode/transform/LN_transform.py index 95c1fea4..7e7edd42 100644 --- a/blender/arm/logicnode/transform/LN_transform.py +++ b/blender/arm/logicnode/transform/LN_transform.py @@ -6,8 +6,7 @@ class TransformNode(ArmLogicTreeNode): bl_label = 'Transform' arm_version = 1 - def init(self, context): - super(TransformNode, self).init(context) + def arm_init(self, context): self.add_input('ArmVectorSocket', 'Location') self.add_input('ArmVectorSocket', 'Rotation') self.add_input('ArmVectorSocket', 'Scale', default_value=[1.0, 1.0, 1.0]) diff --git a/blender/arm/logicnode/transform/LN_transform_math.py b/blender/arm/logicnode/transform/LN_transform_math.py index 70a7b5dc..bc5a90a2 100644 --- a/blender/arm/logicnode/transform/LN_transform_math.py +++ b/blender/arm/logicnode/transform/LN_transform_math.py @@ -6,8 +6,7 @@ class TransformMathNode(ArmLogicTreeNode): bl_label = 'Transform Math' arm_version = 1 - def init(self, context): - super(TransformMathNode, self).init(context) + def arm_init(self, context): self.add_input('ArmDynamicSocket', 'Transform 1') self.add_input('ArmDynamicSocket', 'Transform 2') diff --git a/blender/arm/logicnode/transform/LN_transform_to_vector.py b/blender/arm/logicnode/transform/LN_transform_to_vector.py index 9b01681d..222f8abb 100644 --- a/blender/arm/logicnode/transform/LN_transform_to_vector.py +++ b/blender/arm/logicnode/transform/LN_transform_to_vector.py @@ -6,8 +6,7 @@ class VectorFromTransformNode(ArmLogicTreeNode): bl_label = 'Transform to Vector' arm_version = 1 - def init(self, context): - super(VectorFromTransformNode, self).init(context) + def arm_init(self, context): self.add_input('ArmDynamicSocket', 'Transform') self.add_output('ArmVectorSocket', 'Vector') diff --git a/blender/arm/logicnode/transform/LN_translate_object.py b/blender/arm/logicnode/transform/LN_translate_object.py index 9076e9ad..d5964666 100644 --- a/blender/arm/logicnode/transform/LN_translate_object.py +++ b/blender/arm/logicnode/transform/LN_translate_object.py @@ -7,8 +7,7 @@ class TranslateObjectNode(ArmLogicTreeNode): arm_section = 'location' arm_version = 1 - def init(self, context): - super(TranslateObjectNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmVectorSocket', 'Vector') diff --git a/blender/arm/logicnode/transform/LN_translate_on_local_axis.py b/blender/arm/logicnode/transform/LN_translate_on_local_axis.py index 35456dad..08192fe6 100644 --- a/blender/arm/logicnode/transform/LN_translate_on_local_axis.py +++ b/blender/arm/logicnode/transform/LN_translate_on_local_axis.py @@ -8,8 +8,7 @@ class TranslateOnLocalAxisNode(ArmLogicTreeNode): arm_section = 'location' arm_version = 1 - def init(self, context): - super(TranslateOnLocalAxisNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmFloatSocket', 'Speed') diff --git a/blender/arm/logicnode/transform/LN_vector_to_object_orientation.py b/blender/arm/logicnode/transform/LN_vector_to_object_orientation.py index 2b064684..e9f0ef6c 100644 --- a/blender/arm/logicnode/transform/LN_vector_to_object_orientation.py +++ b/blender/arm/logicnode/transform/LN_vector_to_object_orientation.py @@ -1,7 +1,7 @@ from arm.logicnode.arm_nodes import * class VectorToObjectOrientationNode(ArmLogicTreeNode): - """Converts the given world vector to a vector oriented by the given object. + """Converts the given world vector to a vector oriented by the given object. The object scale is taken in count. @seeNode World Vector To Object Space @@ -13,8 +13,7 @@ class VectorToObjectOrientationNode(ArmLogicTreeNode): arm_section = 'location' arm_version = 1 - def init(self, context): - super(VectorToObjectOrientationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmVectorSocket', 'World') diff --git a/blender/arm/logicnode/transform/LN_world_vector_to_local_space.py b/blender/arm/logicnode/transform/LN_world_vector_to_local_space.py index 0895659e..c0e0d20a 100644 --- a/blender/arm/logicnode/transform/LN_world_vector_to_local_space.py +++ b/blender/arm/logicnode/transform/LN_world_vector_to_local_space.py @@ -1,7 +1,7 @@ from arm.logicnode.arm_nodes import * class WorldVectorToLocalSpaceNode(ArmLogicTreeNode): - """Converts the given world vector to a object space vector. + """Converts the given world vector to a object space vector. The object scale is taken in count. @seeNode Vector To Object Orientation @@ -13,8 +13,7 @@ class WorldVectorToLocalSpaceNode(ArmLogicTreeNode): arm_section = 'location' arm_version = 1 - def init(self, context): - super(WorldVectorToLocalSpaceNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmVectorSocket', 'World') diff --git a/blender/arm/logicnode/variable/LN_boolean.py b/blender/arm/logicnode/variable/LN_boolean.py index 810cdfc9..f1769d8f 100644 --- a/blender/arm/logicnode/variable/LN_boolean.py +++ b/blender/arm/logicnode/variable/LN_boolean.py @@ -7,8 +7,7 @@ class BooleanNode(ArmLogicTreeNode): bl_label = 'Boolean' arm_version = 1 - def init(self, context): - super(BooleanNode, self).init(context) + def arm_init(self, context): self.add_input('ArmBoolSocket', 'Bool In') self.add_output('ArmBoolSocket', 'Bool Out', is_var=True) diff --git a/blender/arm/logicnode/variable/LN_color.py b/blender/arm/logicnode/variable/LN_color.py index 3f7f805e..92ff7140 100644 --- a/blender/arm/logicnode/variable/LN_color.py +++ b/blender/arm/logicnode/variable/LN_color.py @@ -6,8 +6,7 @@ class ColorNode(ArmLogicTreeNode): bl_label = 'Color' arm_version = 1 - def init(self, context): - super(ColorNode, self).init(context) + def arm_init(self, context): self.add_input('ArmColorSocket', 'Color In', default_value=[1.0, 1.0, 1.0, 1.0]) self.add_output('ArmColorSocket', 'Color Out', is_var=True) diff --git a/blender/arm/logicnode/variable/LN_dynamic.py b/blender/arm/logicnode/variable/LN_dynamic.py index 11e1e73e..e1465c99 100644 --- a/blender/arm/logicnode/variable/LN_dynamic.py +++ b/blender/arm/logicnode/variable/LN_dynamic.py @@ -6,6 +6,5 @@ class DynamicNode(ArmLogicTreeNode): bl_label = 'Dynamic' arm_version = 1 - def init(self, context): - super(DynamicNode, self).init(context) + def arm_init(self, context): self.add_output('ArmDynamicSocket', 'Dynamic', is_var=True) diff --git a/blender/arm/logicnode/variable/LN_float.py b/blender/arm/logicnode/variable/LN_float.py index b660e53f..abd2caae 100644 --- a/blender/arm/logicnode/variable/LN_float.py +++ b/blender/arm/logicnode/variable/LN_float.py @@ -9,7 +9,6 @@ class FloatNode(ArmLogicTreeNode): bl_label = 'Float' arm_version = 1 - def init(self, context): - super(FloatNode, self).init(context) + def arm_init(self, context): self.add_input('ArmFloatSocket', 'Float In') self.add_output('ArmFloatSocket', 'Float Out', is_var=True) diff --git a/blender/arm/logicnode/variable/LN_integer.py b/blender/arm/logicnode/variable/LN_integer.py index fb16b2d5..89dd5d54 100644 --- a/blender/arm/logicnode/variable/LN_integer.py +++ b/blender/arm/logicnode/variable/LN_integer.py @@ -6,7 +6,6 @@ class IntegerNode(ArmLogicTreeNode): bl_label = 'Integer' arm_version = 1 - def init(self, context): - super(IntegerNode, self).init(context) + def arm_init(self, context): self.add_input('ArmIntSocket', 'Int In') self.add_output('ArmIntSocket', 'Int Out', is_var=True) diff --git a/blender/arm/logicnode/variable/LN_mask.py b/blender/arm/logicnode/variable/LN_mask.py index 68ec954f..4be96e2a 100644 --- a/blender/arm/logicnode/variable/LN_mask.py +++ b/blender/arm/logicnode/variable/LN_mask.py @@ -6,8 +6,7 @@ class MaskNode(ArmLogicTreeNode): bl_label = 'Mask' arm_version = 1 - def init(self, context): - super(MaskNode, self).init(context) + def arm_init(self, context): for i in range(1, 21): label = 'Group {:02d}'.format(i) self.inputs.new('ArmBoolSocket', label) diff --git a/blender/arm/logicnode/variable/LN_quaternion.py b/blender/arm/logicnode/variable/LN_quaternion.py index 15eb6ca2..7316dbab 100644 --- a/blender/arm/logicnode/variable/LN_quaternion.py +++ b/blender/arm/logicnode/variable/LN_quaternion.py @@ -7,8 +7,7 @@ class QuaternionNode(ArmLogicTreeNode): arm_section = 'quaternions' arm_version = 1 - def init(self, context): - super(QuaternionNode, self).init(context) + def arm_init(self, context): self.add_input('ArmFloatSocket', 'X') self.add_input('ArmFloatSocket', 'Y') self.add_input('ArmFloatSocket', 'Z') diff --git a/blender/arm/logicnode/variable/LN_set_variable.py b/blender/arm/logicnode/variable/LN_set_variable.py index ae729ee7..411aca6a 100644 --- a/blender/arm/logicnode/variable/LN_set_variable.py +++ b/blender/arm/logicnode/variable/LN_set_variable.py @@ -13,8 +13,7 @@ class SetVariableNode(ArmLogicTreeNode): arm_section = 'set' arm_version = 1 - def init(self, context): - super(SetVariableNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmDynamicSocket', 'Variable', is_var=True) self.add_input('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/variable/LN_vector.py b/blender/arm/logicnode/variable/LN_vector.py index d93cd2c6..19cf02e7 100644 --- a/blender/arm/logicnode/variable/LN_vector.py +++ b/blender/arm/logicnode/variable/LN_vector.py @@ -6,8 +6,7 @@ class VectorNode(ArmLogicTreeNode): bl_label = 'Vector' arm_version = 1 - def init(self, context): - super(VectorNode, self).init(context) + def arm_init(self, context): self.add_input('ArmFloatSocket', 'X') self.add_input('ArmFloatSocket', 'Y') self.add_input('ArmFloatSocket', 'Z') diff --git a/blender/arm/make_logic.py b/blender/arm/make_logic.py index cc6b9a4d..3299e085 100755 --- a/blender/arm/make_logic.py +++ b/blender/arm/make_logic.py @@ -159,7 +159,7 @@ def build_node(node: bpy.types.Node, f: TextIO) -> Optional[str]: # Properties for prop_name in arm.node_utils.get_haxe_property_names(node): - prop = arm.node_utils.haxe_format_prop(node, prop_name) + prop = arm.node_utils.haxe_format_prop_value(node, prop_name) f.write('\t\t' + name + '.' + prop_name + ' = ' + prop + ';\n') # Create inputs @@ -262,7 +262,6 @@ def get_root_nodes(node_group): def build_default_node(inp: bpy.types.NodeSocket): """Creates a new node to give a not connected input socket a value""" - is_custom_socket = isinstance(inp, arm.logicnode.arm_sockets.ArmCustomSocket) if is_custom_socket: diff --git a/blender/arm/node_utils.py b/blender/arm/node_utils.py index 2a9621fe..747360d5 100755 --- a/blender/arm/node_utils.py +++ b/blender/arm/node_utils.py @@ -151,7 +151,7 @@ def haxe_format_socket_val(socket_val: Any, array_outer_brackets=True) -> str: return str(socket_val) -def haxe_format_prop(node: bpy.types.Node, prop_name: str) -> str: +def haxe_format_prop_value(node: bpy.types.Node, prop_name: str) -> str: """Formats a property value to be valid Haxe syntax.""" prop_value = getattr(node, prop_name) if isinstance(prop_value, str): diff --git a/blender/arm/write_data.py b/blender/arm/write_data.py index 4d2aadda..8e2e01ad 100755 --- a/blender/arm/write_data.py +++ b/blender/arm/write_data.py @@ -174,6 +174,9 @@ project.addSources('Sources'); import_traits.append('armory.trait.internal.Bridge') if live_patch: assets.add_khafile_def('arm_patch') + # Include all logic node classes so that they can later + # get instantiated + khafile.write("""project.addParameter("--macro include('armory.logicnode')");\n""") import_traits = list(set(import_traits)) for i in range(0, len(import_traits)): From 4f13ebc439918b66406f74c1f4ed42d7b4dc02bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Tue, 20 Jul 2021 23:33:47 +0200 Subject: [PATCH 222/264] Live patch: fix updates of object sockets --- Sources/armory/logicnode/ObjectNode.hx | 2 +- blender/arm/live_patch.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/armory/logicnode/ObjectNode.hx b/Sources/armory/logicnode/ObjectNode.hx index 1506fdd2..bd3e8494 100644 --- a/Sources/armory/logicnode/ObjectNode.hx +++ b/Sources/armory/logicnode/ObjectNode.hx @@ -21,7 +21,7 @@ class ObjectNode extends LogicNode { override function set(value: Dynamic) { if (inputs.length > 0) inputs[0].set(value); else { - objectName = value.name; + objectName = value != null ? value.name : ""; this.value = value; } } diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index 7bc8073d..a6527e23 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -209,6 +209,8 @@ def send_event(event_id: str, opt_data: Any = None): value = '{' + f'"x": {value[0]}, "y": {value[1]}, "z": {value[2]}' + '}' elif inp_type == 'RGBA': value = '{' + f'"x": {value[0]}, "y": {value[1]}, "z": {value[2]}, "w": {value[3]}' + '}' + elif inp_type == 'OBJECT': + value = f'iron.Scene.active.getChild("{value}")' if value != '' else 'null' else: value = arm.node_utils.haxe_format_socket_val(value) From 5276711094d3943480c2125d639e1b2171f9f480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Tue, 20 Jul 2021 23:34:06 +0200 Subject: [PATCH 223/264] Live patch: add more ignored operators --- blender/arm/live_patch.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index a6527e23..9079cfde 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -300,9 +300,11 @@ IGNORE_OPERATORS = ( 'VIEW3D_OT_select_box', 'OUTLINER_OT_item_activate', 'OBJECT_OT_editmode_toggle', + 'NODE_OT_delete', 'NODE_OT_duplicate_move', 'NODE_OT_link', 'NODE_OT_select', 'NODE_OT_translate_attach_remove_on_cancel', - 'NODE_OT_translate_attach' + 'NODE_OT_translate_attach', + 'UI_OT_button_string_clear' ) From 9ff726bac1c8d9f6a3410401592640d4b0ca0899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Wed, 21 Jul 2021 00:01:27 +0200 Subject: [PATCH 224/264] Fix usage of multiple UV maps on mobile render path --- blender/arm/material/make_attrib.py | 37 +++++++++++++++++++++++++++-- blender/arm/material/make_mesh.py | 35 ++------------------------- 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/blender/arm/material/make_attrib.py b/blender/arm/material/make_attrib.py index 6749d854..bc23a620 100644 --- a/blender/arm/material/make_attrib.py +++ b/blender/arm/material/make_attrib.py @@ -1,9 +1,12 @@ +from typing import Optional + import arm.material.cycles as cycles import arm.material.mat_state as mat_state import arm.material.make_skin as make_skin import arm.material.make_particle as make_particle import arm.material.make_inst as make_inst -import arm.material.shader as shader +import arm.material.make_tess as make_tess +from arm.material.shader import Shader, ShaderContext import arm.utils @@ -33,7 +36,7 @@ def write_vertpos(vert): vert.write('gl_Position = WVP * spos;') -def write_norpos(con_mesh: shader.ShaderContext, vert: shader.Shader, declare=False, write_nor=True): +def write_norpos(con_mesh: ShaderContext, vert: Shader, declare=False, write_nor=True): is_bone = con_mesh.is_elem('bone') if is_bone: make_skin.skin_pos(vert) @@ -45,3 +48,33 @@ def write_norpos(con_mesh: shader.ShaderContext, vert: shader.Shader, declare=Fa vert.write_attrib(prep + 'wnormal = normalize(N * vec3(nor.xy, pos.w));') if con_mesh.is_elem('ipos'): make_inst.inst_pos(con_mesh, vert) + + +def write_tex_coords(con_mesh: ShaderContext, vert: Shader, frag: Shader, tese: Optional[Shader]): + rpdat = arm.utils.get_rp() + + if con_mesh.is_elem('tex'): + vert.add_out('vec2 texCoord') + vert.add_uniform('float texUnpack', link='_texUnpack') + if mat_state.material.arm_tilesheet_flag: + if mat_state.material.arm_particle_flag and rpdat.arm_particles == 'On': + make_particle.write_tilesheet(vert) + else: + vert.add_uniform('vec2 tilesheetOffset', '_tilesheetOffset') + vert.write_attrib('texCoord = tex * texUnpack + tilesheetOffset;') + else: + vert.write_attrib('texCoord = tex * texUnpack;') + + if tese is not None: + tese.write_pre = True + make_tess.interpolate(tese, 'texCoord', 2, declare_out=frag.contains('texCoord')) + tese.write_pre = False + + if con_mesh.is_elem('tex1'): + vert.add_out('vec2 texCoord1') + vert.add_uniform('float texUnpack', link='_texUnpack') + vert.write_attrib('texCoord1 = tex1 * texUnpack;') + if tese is not None: + tese.write_pre = True + make_tess.interpolate(tese, 'texCoord1', 2, declare_out=frag.contains('texCoord1')) + tese.write_pre = False diff --git a/blender/arm/material/make_mesh.py b/blender/arm/material/make_mesh.py index 275fa0e0..db9a2c28 100644 --- a/blender/arm/material/make_mesh.py +++ b/blender/arm/material/make_mesh.py @@ -132,31 +132,7 @@ def make_base(con_mesh, parse_opacity): if not is_displacement and not vattr_written: make_attrib.write_vertpos(vert) - if con_mesh.is_elem('tex'): - vert.add_out('vec2 texCoord') - vert.add_uniform('float texUnpack', link='_texUnpack') - if mat_state.material.arm_tilesheet_flag: - if mat_state.material.arm_particle_flag and rpdat.arm_particles == 'On': - make_particle.write_tilesheet(vert) - else: - vert.add_uniform('vec2 tilesheetOffset', '_tilesheetOffset') - vert.write_attrib('texCoord = tex * texUnpack + tilesheetOffset;') - else: - vert.write_attrib('texCoord = tex * texUnpack;') - - if tese is not None: - tese.write_pre = True - make_tess.interpolate(tese, 'texCoord', 2, declare_out=frag.contains('texCoord')) - tese.write_pre = False - - if con_mesh.is_elem('tex1'): - vert.add_out('vec2 texCoord1') - vert.add_uniform('float texUnpack', link='_texUnpack') - vert.write_attrib('texCoord1 = tex1 * texUnpack;') - if tese is not None: - tese.write_pre = True - make_tess.interpolate(tese, 'texCoord1', 2, declare_out=frag.contains('texCoord1')) - tese.write_pre = False + make_attrib.write_tex_coords(con_mesh, vert, frag, tese) if con_mesh.is_elem('col'): vert.add_out('vec3 vcolor') @@ -318,14 +294,7 @@ def make_forward_mobile(con_mesh): opac = mat_state.material.arm_discard_opacity frag.write('if (opacity < {0}) discard;'.format(opac)) - if con_mesh.is_elem('tex'): - vert.add_out('vec2 texCoord') - vert.add_uniform('float texUnpack', link='_texUnpack') - if mat_state.material.arm_tilesheet_flag: - vert.add_uniform('vec2 tilesheetOffset', '_tilesheetOffset') - vert.write('texCoord = tex * texUnpack + tilesheetOffset;') - else: - vert.write('texCoord = tex * texUnpack;') + make_attrib.write_tex_coords(con_mesh, vert, frag, tese) if con_mesh.is_elem('col'): vert.add_out('vec3 vcolor') From 256d27e289d6596b1c1ba647102bb6df9c199a23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Wed, 21 Jul 2021 22:58:21 +0200 Subject: [PATCH 225/264] Fix vector/color handling for logic node live patching --- Sources/armory/trait/internal/Bridge.hx | 1 + blender/arm/live_patch.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/armory/trait/internal/Bridge.hx b/Sources/armory/trait/internal/Bridge.hx index 1ce35562..632fad8f 100644 --- a/Sources/armory/trait/internal/Bridge.hx +++ b/Sources/armory/trait/internal/Bridge.hx @@ -11,6 +11,7 @@ class Bridge { public static var Input = iron.system.Input; public static var Object = iron.object.Object; public static var Data = iron.data.Data; + public static var Vec4 = iron.math.Vec4; public static function log(s: String) { trace(s); }; } diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index 9079cfde..99314c54 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -206,9 +206,9 @@ def send_event(event_id: str, opt_data: Any = None): inp_type = socket.arm_socket_type if inp_type in ('VECTOR', 'RGB'): - value = '{' + f'"x": {value[0]}, "y": {value[1]}, "z": {value[2]}' + '}' + value = f'new iron.Vec4({arm.node_utils.haxe_format_socket_val(value, array_outer_brackets=False)}, 1.0)' elif inp_type == 'RGBA': - value = '{' + f'"x": {value[0]}, "y": {value[1]}, "z": {value[2]}, "w": {value[3]}' + '}' + value = f'new iron.Vec4({arm.node_utils.haxe_format_socket_val(value, array_outer_brackets=False)})' elif inp_type == 'OBJECT': value = f'iron.Scene.active.getChild("{value}")' if value != '' else 'null' else: From aa214022211757caa94fd53951c47f2454e5042b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 24 Jul 2021 13:36:16 +0200 Subject: [PATCH 226/264] Live patch: handle deletion of node links --- Sources/armory/logicnode/LogicNode.hx | 162 +++++++++++++++++---- Sources/armory/logicnode/QuaternionNode.hx | 14 +- Sources/armory/logicnode/SetParentNode.hx | 6 +- Sources/armory/logicnode/VectorNode.hx | 6 +- Sources/armory/trait/internal/LivePatch.hx | 77 +++++++--- blender/arm/live_patch.py | 47 ++++++ blender/arm/logicnode/arm_nodes.py | 41 +++++- blender/arm/make_logic.py | 39 +++-- 8 files changed, 312 insertions(+), 80 deletions(-) diff --git a/Sources/armory/logicnode/LogicNode.hx b/Sources/armory/logicnode/LogicNode.hx index 38dac54b..18b3602d 100644 --- a/Sources/armory/logicnode/LogicNode.hx +++ b/Sources/armory/logicnode/LogicNode.hx @@ -4,8 +4,8 @@ package armory.logicnode; class LogicNode { var tree: LogicTree; - var inputs: Array = []; - var outputs: Array> = []; + var inputs: Array = []; + var outputs: Array> = []; #if (arm_debug || arm_patch) public var name = ""; @@ -22,14 +22,115 @@ class LogicNode { this.tree = tree; } - public function addInput(node: LogicNode, from: Int) { - inputs.push(new LogicNodeInput(node, from)); + /** + Resize the inputs array to a given size to minimize dynamic + reallocation and over-allocation later. + **/ + inline function preallocInputs(amount: Int) { + this.inputs.resize(amount); } - public function addOutputs(nodes: Array) { - outputs.push(nodes); + /** + Resize the outputs array to a given size to minimize dynamic + reallocation and over-allocation later. + **/ + inline function preallocOutputs(amount: Int) { + this.outputs.resize(amount); + for (i in 0...outputs.length) { + outputs[i] = []; + } } + /** + Add a link between to nodes to the tree. + **/ + public static function addLink(fromNode: LogicNode, toNode: LogicNode, fromIndex: Int, toIndex: Int): LogicNodeLink { + var link = new LogicNodeLink(fromNode, toNode, fromIndex, toIndex); + + if (toNode.inputs.length <= toIndex) { + toNode.inputs.resize(toIndex + 1); + } + toNode.inputs[toIndex] = link; + + var fromNodeOuts = fromNode.outputs; + var outLen = fromNodeOuts.length; + if (outLen <= fromIndex) { + fromNodeOuts.resize(fromIndex + 1); + + // Initialize with empty arrays + for (i in 0...(fromIndex - (outLen - 1))) { + fromNodeOuts[i] = []; + } + } + fromNodeOuts[fromIndex].push(link); + + return link; + } + + #if arm_patch + /** + Removes a link from the tree. + **/ + static function removeLink(link: LogicNodeLink) { + link.fromNode.outputs[link.fromIndex].remove(link); + + // Reuse the same link and connect a default input node to it. + // That's why this function is only available in arm_patch mode, we need + // access to the link's type and value. + link.fromNode = LogicNode.createSocketDefaultNode(link.toNode.tree, link.toType, link.toValue); + link.fromIndex = 0; + } + + /** + Removes all inputs and their links from this node. + Warning: this function changes the amount of node inputs to 0! + **/ + function clearInputs() { + for (link in inputs) { + link.fromNode.outputs[link.fromIndex].remove(link); + } + inputs.resize(0); + } + + /** + Removes all outputs and their links from this node. + Warning: this function changes the amount of node inputs to 0! + **/ + function clearOutputs() { + for (links in outputs) { + for (link in links) { + var defaultNode = LogicNode.createSocketDefaultNode(tree, link.toType, link.toValue); + link.fromNode = defaultNode; + link.fromIndex = 0; + defaultNode.outputs[0] = [link]; + } + } + outputs.resize(0); + } + + /** + Creates a default node for a socket so that get() and set() can be + used without null checks. + Loosely equivalent to `make_logic.build_default_node()` in Python. + **/ + static inline function createSocketDefaultNode(tree: LogicTree, socketType: String, value: Dynamic): LogicNode { + // Make sure to not add these nodes to the LogicTree.nodes array as they + // won't be garbage collected then if unlinked later. + return switch (socketType) { + case "VECTOR": new armory.logicnode.VectorNode(tree, value[0], value[1], value[2]); + case "RGBA": new armory.logicnode.ColorNode(tree, value[0], value[1], value[2], value[3]); + case "RGB": new armory.logicnode.ColorNode(tree, value[0], value[1], value[2]); + case "VALUE": new armory.logicnode.FloatNode(tree, value); + case "INT": new armory.logicnode.IntegerNode(tree, value); + case "BOOLEAN": new armory.logicnode.BooleanNode(tree, value); + case "STRING": new armory.logicnode.StringNode(tree, value); + case "NONE": new armory.logicnode.NullNode(tree); + case "OBJECT": new armory.logicnode.ObjectNode(tree, value); + default: new armory.logicnode.DynamicNode(tree, value); + } + } + #end + /** Called when this node is activated. @param from impulse index @@ -42,42 +143,45 @@ class LogicNode { **/ function runOutput(i: Int) { if (i >= outputs.length) return; - for (o in outputs[i]) { - // Check which input activated the node - for (j in 0...o.inputs.length) { - if (o.inputs[j].node == this) { - o.run(j); - break; - } - } + for (outLink in outputs[i]) { + outLink.toNode.run(outLink.toIndex); } } - @:allow(armory.logicnode.LogicNodeInput) + @:allow(armory.logicnode.LogicNodeLink) function get(from: Int): Dynamic { return this; } - @:allow(armory.logicnode.LogicNodeInput) + @:allow(armory.logicnode.LogicNodeLink) function set(value: Dynamic) {} } -class LogicNodeInput { +@:allow(armory.logicnode.LogicNode) +@:allow(armory.logicnode.LogicTree) +class LogicNodeLink { - @:allow(armory.logicnode.LogicNode) - var node: LogicNode; - var from: Int; // Socket index + var fromNode: LogicNode; + var toNode: LogicNode; + var fromIndex: Int; + var toIndex: Int; - public function new(node: LogicNode, from: Int) { - this.node = node; - this.from = from; + #if arm_patch + var fromType: String; + var toType: String; + var toValue: Dynamic; + #end + + inline function new(fromNode: LogicNode, toNode: LogicNode, fromIndex: Int, toIndex: Int) { + this.fromNode = fromNode; + this.toNode = toNode; + this.fromIndex = fromIndex; + this.toIndex = toIndex; } - @:allow(armory.logicnode.LogicNode) - function get(): Dynamic { - return node.get(from); + inline function get(): Dynamic { + return fromNode.get(fromIndex); } - @:allow(armory.logicnode.LogicNode) - function set(value: Dynamic) { - node.set(value); + inline function set(value: Dynamic) { + fromNode.set(value); } } diff --git a/Sources/armory/logicnode/QuaternionNode.hx b/Sources/armory/logicnode/QuaternionNode.hx index 95880f4f..bf83f4de 100644 --- a/Sources/armory/logicnode/QuaternionNode.hx +++ b/Sources/armory/logicnode/QuaternionNode.hx @@ -11,10 +11,10 @@ class QuaternionNode extends LogicNode { super(tree); if (x != null) { - addInput(new FloatNode(tree, x), 0); - addInput(new FloatNode(tree, y), 0); - addInput(new FloatNode(tree, z), 0); - addInput(new FloatNode(tree, w), 0); + LogicNode.addLink(new FloatNode(tree, x), this, 0, 0); + LogicNode.addLink(new FloatNode(tree, y), this, 0, 1); + LogicNode.addLink(new FloatNode(tree, z), this, 0, 2); + LogicNode.addLink(new FloatNode(tree, w), this, 0, 3); } } @@ -27,15 +27,15 @@ class QuaternionNode extends LogicNode { switch (from){ case 0: return value; - case 1: - var value1 = new Vec4(); + case 1: + var value1 = new Vec4(); value1.x = value.x; value1.y = value.y; value1.z = value.z; value1.w = 0; // use 0 to avoid this vector being translated. return value1; case 2: - return value.w; + return value.w; default: return null; } diff --git a/Sources/armory/logicnode/SetParentNode.hx b/Sources/armory/logicnode/SetParentNode.hx index 8d37720d..457facad 100644 --- a/Sources/armory/logicnode/SetParentNode.hx +++ b/Sources/armory/logicnode/SetParentNode.hx @@ -14,8 +14,8 @@ class SetParentNode extends LogicNode { var parent: Object; var isUnparent = false; - if (Std.is(inputs[2].node, ObjectNode)) { - var parentNode = cast(inputs[2].node, ObjectNode); + if (Std.is(inputs[2].fromNode, ObjectNode)) { + var parentNode = cast(inputs[2].fromNode, ObjectNode); isUnparent = parentNode.objectName == ""; } if (isUnparent) parent = iron.Scene.active.root; @@ -24,7 +24,7 @@ class SetParentNode extends LogicNode { if (object == null || parent == null || object.parent == parent) return; object.parent.removeChild(object, isUnparent); // keepTransform - + #if arm_physics var rigidBody = object.getTrait(RigidBody); if (rigidBody != null) rigidBody.setActivationState(0); diff --git a/Sources/armory/logicnode/VectorNode.hx b/Sources/armory/logicnode/VectorNode.hx index 2c186907..09cb0204 100644 --- a/Sources/armory/logicnode/VectorNode.hx +++ b/Sources/armory/logicnode/VectorNode.hx @@ -10,9 +10,9 @@ class VectorNode extends LogicNode { super(tree); if (x != null) { - addInput(new FloatNode(tree, x), 0); - addInput(new FloatNode(tree, y), 0); - addInput(new FloatNode(tree, z), 0); + LogicNode.addLink(new FloatNode(tree, x), this, 0, 0); + LogicNode.addLink(new FloatNode(tree, y), this, 0, 1); + LogicNode.addLink(new FloatNode(tree, z), this, 0, 2); } } diff --git a/Sources/armory/trait/internal/LivePatch.hx b/Sources/armory/trait/internal/LivePatch.hx index 550acb5b..fe486bc4 100644 --- a/Sources/armory/trait/internal/LivePatch.hx +++ b/Sources/armory/trait/internal/LivePatch.hx @@ -6,7 +6,7 @@ import armory.logicnode.LogicTree; #if arm_patch @:expose("LivePatch") #end @:access(armory.logicnode.LogicNode) -@:access(armory.logicnode.LogicNodeInput) +@:access(armory.logicnode.LogicNodeLink) class LivePatch extends iron.Trait { #if !arm_patch @@ -40,11 +40,54 @@ class LivePatch extends iron.Trait { var toNode = tree.nodes[toNodeName]; if (fromNode == null || toNode == null) return; - // Don't add a connection twice - if (!fromNode.outputs[fromIndex].contains(toNode)) { - fromNode.outputs[fromIndex].push(toNode); + LogicNode.addLink(fromNode, toNode, fromIndex, toIndex); + } + + public static function patchSetNodeLinks(treeName: String, nodeName: String, inputDatas: Array, outputDatas: Array>) { + var tree = LogicTree.nodeTrees[treeName]; + if (tree == null) return; + + var node = tree.nodes[nodeName]; + if (node == null) return; + + node.clearInputs(); + node.clearOutputs(); + + for (inputData in inputDatas) { + var fromNode: LogicNode; + var fromIndex: Int; + + if (inputData.isLinked) { + fromNode = tree.nodes[inputData.fromNode]; + if (fromNode == null) continue; + fromIndex = inputData.fromIndex; + } + else { + fromNode = LogicNode.createSocketDefaultNode(node.tree, inputData.socketType, inputData.socketValue); + fromIndex = 0; + } + + LogicNode.addLink(fromNode, node, fromIndex, inputData.toIndex); + } + + for (outputData in outputDatas) { + for (linkData in outputData) { + var toNode: LogicNode; + var toIndex: Int; + + if (linkData.isLinked) { + toNode = tree.nodes[linkData.toNode]; + if (toNode == null) continue; + toIndex = linkData.toIndex; + } + else { + toNode = LogicNode.createSocketDefaultNode(node.tree, linkData.socketType, linkData.socketValue); + toIndex = 0; + } + + LogicNode.addLink(node, toNode, linkData.fromIndex, toIndex); + } } - toNode.inputs[toIndex] = new LogicNodeInput(fromNode, fromIndex); } public static function patchUpdateNodeProp(treeName: String, nodeName: String, propName: String, value: Dynamic) { @@ -123,11 +166,12 @@ class LivePatch extends iron.Trait { var i = 0; for (inputData in inputDatas) { - newNode.addInput(createSocketDefaultNode(newNode.tree, inputData[0], inputData[1]), i++); + LogicNode.addLink(LogicNode.createSocketDefaultNode(newNode.tree, inputData[0], inputData[1]), newNode, 0, i++); } + i = 0; for (outputData in outputDatas) { - newNode.addOutputs([createSocketDefaultNode(newNode.tree, outputData[0], outputData[1])]); + LogicNode.addLink(newNode, LogicNode.createSocketDefaultNode(newNode.tree, outputData[0], outputData[1]), i++, 0); } } @@ -151,27 +195,14 @@ class LivePatch extends iron.Trait { var i = 0; for (inputData in inputDatas) { - newNode.addInput(createSocketDefaultNode(newNode.tree, inputData[0], inputData[1]), i++); + LogicNode.addLink(LogicNode.createSocketDefaultNode(newNode.tree, inputData[0], inputData[1]), newNode, 0, i++); } + i = 0; for (outputData in outputDatas) { - newNode.addOutputs([createSocketDefaultNode(newNode.tree, outputData[0], outputData[1])]); + LogicNode.addLink(newNode, LogicNode.createSocketDefaultNode(newNode.tree, outputData[0], outputData[1]), i++, 0); } } - static inline function createSocketDefaultNode(tree: LogicTree, socketType: String, value: Dynamic): LogicNode { - return switch (socketType) { - case "VECTOR": new armory.logicnode.VectorNode(tree, value[0], value[1], value[2]); - case "RGBA": new armory.logicnode.ColorNode(tree, value[0], value[1], value[2], value[3]); - case "RGB": new armory.logicnode.ColorNode(tree, value[0], value[1], value[2]); - case "VALUE": new armory.logicnode.FloatNode(tree, value); - case "INT": new armory.logicnode.IntegerNode(tree, value); - case "BOOLEAN": new armory.logicnode.BooleanNode(tree, value); - case "STRING": new armory.logicnode.StringNode(tree, value); - case "NONE": new armory.logicnode.NullNode(tree); - case "OBJECT": new armory.logicnode.ObjectNode(tree, value); - default: new armory.logicnode.DynamicNode(tree, value); - } - } #end } diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index 99314c54..ba60b13f 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -269,6 +269,53 @@ def send_event(event_id: str, opt_data: Any = None): js = f'LivePatch.patchNodeCopy("{tree_name}", "{node_name}", "{newnode_name}", {props_list}, {inp_data}, {out_data});' write_patch(js) + elif event_id == 'ln_update_sockets': + node: ArmLogicTreeNode = opt_data + + tree_name = arm.node_utils.get_export_tree_name(node.get_tree()) + node_name = arm.node_utils.get_export_node_name(node)[1:] + + inp_data = '[' + for idx, inp in enumerate(node.inputs): + inp_data += '{' + # is_linked can be true even if there are no links if the + # user starts dragging a connection away before releasing + # the mouse + if inp.is_linked and len(inp.links) > 0: + inp_data += 'isLinked: true,' + inp_data += f'fromNode: "{arm.node_utils.get_export_node_name(inp.links[0].from_node)[1:]}",' + inp_data += f'fromIndex: {arm.node_utils.get_socket_index(inp.links[0].from_node.outputs, inp.links[0].from_socket)},' + else: + inp_data += 'isLinked: false,' + inp_data += f'socketType: "{inp.arm_socket_type}",' + inp_data += f'socketValue: {arm.node_utils.haxe_format_socket_val(inp.get_default_value())},' + + inp_data += f'toIndex: {idx}' + inp_data += '},' + inp_data += ']' + + out_data = '[' + for idx, out in enumerate(node.outputs): + out_data += '[' + for link in out.links: + out_data += '{' + if out.is_linked: + out_data += 'isLinked: true,' + out_data += f'toNode: "{arm.node_utils.get_export_node_name(link.to_node)[1:]}",' + out_data += f'toIndex: {arm.node_utils.get_socket_index(link.to_node.inputs, link.to_socket)},' + else: + out_data += 'isLinked: false,' + out_data += f'socketType: "{out.arm_socket_type}",' + out_data += f'socketValue: {arm.node_utils.haxe_format_socket_val(out.get_default_value())},' + + out_data += f'fromIndex: {idx}' + out_data += '},' + out_data += '],' + out_data += ']' + + js = f'LivePatch.patchSetNodeLinks("{tree_name}", "{node_name}", {inp_data}, {out_data});' + write_patch(js) + def on_operator(operator_id: str): """As long as bpy.msgbus doesn't listen to changes made by diff --git a/blender/arm/logicnode/arm_nodes.py b/blender/arm/logicnode/arm_nodes.py index d7548c5c..5fd365a6 100644 --- a/blender/arm/logicnode/arm_nodes.py +++ b/blender/arm/logicnode/arm_nodes.py @@ -24,6 +24,10 @@ category_items: ODict[str, List['ArmNodeCategory']] = OrderedDict() array_nodes = dict() +# See ArmLogicTreeNode.update() +# format: [tree pointer => (num inputs, num input links, num outputs, num output links)] +last_node_state: dict[int, tuple[int, int, int, int]] = {} + class ArmLogicTreeNode(bpy.types.Node): arm_category = PKG_AS_CATEGORY @@ -62,6 +66,40 @@ class ArmLogicTreeNode(bpy.types.Node): def get_tree(self): return self.id_data + def update(self): + """Called if the node was updated in some way, for example + if socket connections change. This callback is not called if + socket values were changed. + """ + def num_connected(sockets): + return sum([socket.is_linked for socket in sockets]) + + # If a link between sockets is removed, there is currently no + # _reliable_ way in the Blender API to check which connection + # was removed (*). + # + # So instead we just check _if_ the number of links or sockets + # has changed (the update function is called before and after + # each link removal). Because we listen for those updates in + # general, we automatically also listen to link creation events, + # which is more stable than using the dedicated callback for + # that (`insert_link()`), because adding links can remove other + # links and we would need to react to that as well. + # + # (*) https://devtalk.blender.org/t/how-to-detect-which-link-was-deleted-by-user-in-node-editor + + self_id = self.as_pointer() + + current_state = (len(self.inputs), num_connected(self.inputs), len(self.outputs), num_connected(self.outputs)) + if self_id not in last_node_state: + # Lazily initialize the last_node_state dict to also store + # state for nodes that already exist in the tree + last_node_state[self_id] = current_state + + if last_node_state[self_id] != current_state: + arm.live_patch.send_event('ln_update_sockets', self) + last_node_state[self_id] = current_state + def free(self): """Called before the node is deleted.""" arm.live_patch.send_event('ln_delete', self) @@ -84,7 +122,8 @@ class ArmLogicTreeNode(bpy.types.Node): def insert_link(self, link: bpy.types.NodeLink): """Called on *both* nodes when a link between two nodes is created.""" - arm.live_patch.send_event('ln_insert_link', (self, link)) + # arm.live_patch.send_event('ln_insert_link', (self, link)) + pass def get_replacement_node(self, node_tree: bpy.types.NodeTree): # needs to be overridden by individual node classes with arm_version>1 diff --git a/blender/arm/make_logic.py b/blender/arm/make_logic.py index 3299e085..0e594f17 100755 --- a/blender/arm/make_logic.py +++ b/blender/arm/make_logic.py @@ -68,6 +68,7 @@ def build_node_tree(node_group: 'arm.nodes_logic.ArmLogicTree'): with open(file, 'w', encoding="utf-8") as f: f.write('package ' + pack_path + '.node;\n\n') + f.write('@:access(armory.logicnode.LogicNode)') f.write('@:keep class ' + group_name + ' extends armory.logicnode.LogicTree {\n\n') f.write('\tvar functionNodes:Map;\n\n') f.write('\tvar functionOutputNodes:Map;\n\n') @@ -162,8 +163,12 @@ def build_node(node: bpy.types.Node, f: TextIO) -> Optional[str]: prop = arm.node_utils.haxe_format_prop_value(node, prop_name) f.write('\t\t' + name + '.' + prop_name + ' = ' + prop + ';\n') + # Avoid unnecessary input/output array resizes + f.write(f'\t\t{name}.preallocInputs({len(node.inputs)});\n') + f.write(f'\t\t{name}.preallocOutputs({len(node.outputs)});\n') + # Create inputs - for inp in node.inputs: + for idx, inp in enumerate(node.inputs): # True if the input is connected to a unlinked reroute # somewhere down the reroute line unconnected = False @@ -191,34 +196,40 @@ def build_node(node: bpy.types.Node, f: TextIO) -> Optional[str]: for i in range(0, len(n.outputs)): if n.outputs[i] == socket: inp_from = i + from_type = socket.arm_socket_type break # Not linked -> create node with default values else: inp_name = build_default_node(inp) inp_from = 0 + from_type = inp.arm_socket_type # The input is linked to a reroute, but the reroute is unlinked if unconnected: inp_name = build_default_node(inp) inp_from = 0 + from_type = inp.arm_socket_type # Add input - f.write('\t\t' + name + '.addInput(' + inp_name + ', ' + str(inp_from) + ');\n') + f.write(f'\t\t{"var __link = " if wrd.arm_live_patch else ""}armory.logicnode.LogicNode.addLink({inp_name}, {name}, {inp_from}, {idx});\n') + if wrd.arm_live_patch: + to_type = inp.arm_socket_type + f.write(f'\t\t__link.fromType = "{from_type}";') + f.write(f'\t\t__link.toType = "{to_type}";') + f.write(f'\t\t__link.toValue = {arm.node_utils.haxe_format_socket_val(inp.get_default_value())};') # Create outputs - for out in node.outputs: - if out.is_linked: - out_name = '' - for node in collect_nodes_from_output(out, f): - out_name += '[' if len(out_name) == 0 else ', ' - out_name += node - out_name += ']' - # Not linked - create node with default values - else: - out_name = '[' + build_default_node(out) + ']' - # Add outputs - f.write('\t\t' + name + '.addOutputs(' + out_name + ');\n') + for idx, out in enumerate(node.outputs): + # Linked outputs are already handled after iterating over inputs + # above, so only unconnected outputs are handled here + if not out.is_linked: + f.write(f'\t\t{"var __link = " if wrd.arm_live_patch else ""}armory.logicnode.LogicNode.addLink({name}, {build_default_node(out)}, {idx}, 0);\n') + if wrd.arm_live_patch: + out_type = out.arm_socket_type + f.write(f'\t\t__link.fromType = "{out_type}";') + f.write(f'\t\t__link.toType = "{out_type}";') + f.write(f'\t\t__link.toValue = {arm.node_utils.haxe_format_socket_val(out.get_default_value())};') return name From 7eced3b4f57a9ee40fb01deaab6bb52f89da2b5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 24 Jul 2021 13:36:55 +0200 Subject: [PATCH 227/264] Live patch: don't try to re-export if live patch is not active --- blender/arm/live_patch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index ba60b13f..d48f2f3e 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -52,7 +52,7 @@ def stop(): def patch_export(): """Re-export the current scene and update the game accordingly.""" - if state.proc_build is not None: + if not __running or state.proc_build is not None: return arm.assets.invalidate_enabled = False From 2b6a7a4f78172ca3502437e1d90aeb468ea5cc4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 24 Jul 2021 13:37:13 +0200 Subject: [PATCH 228/264] Live patch: add another ignored operator --- blender/arm/live_patch.py | 1 + 1 file changed, 1 insertion(+) diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index d48f2f3e..4bb0be09 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -349,6 +349,7 @@ IGNORE_OPERATORS = ( 'OBJECT_OT_editmode_toggle', 'NODE_OT_delete', 'NODE_OT_duplicate_move', + 'NODE_OT_hide_toggle', 'NODE_OT_link', 'NODE_OT_select', 'NODE_OT_translate_attach_remove_on_cancel', From 82c7302dd92826473171e92355c347e7493e93c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 24 Jul 2021 13:40:39 +0200 Subject: [PATCH 229/264] Live patch: simplify node deletion code --- Sources/armory/trait/internal/LivePatch.hx | 33 ++-------------------- blender/arm/live_patch.py | 5 +--- 2 files changed, 4 insertions(+), 34 deletions(-) diff --git a/Sources/armory/trait/internal/LivePatch.hx b/Sources/armory/trait/internal/LivePatch.hx index fe486bc4..b9beccbe 100644 --- a/Sources/armory/trait/internal/LivePatch.hx +++ b/Sources/armory/trait/internal/LivePatch.hx @@ -110,42 +110,15 @@ class LivePatch extends iron.Trait { @:privateAccess node.inputs[socketIndex].set(value); } - public static function patchNodeDelete(treeName: String, nodeName: String, outputDatas: Array>) { + public static function patchNodeDelete(treeName: String, nodeName: String) { var tree = LogicTree.nodeTrees[treeName]; if (tree == null) return; var node = tree.nodes[nodeName]; if (node == null) return; - // Remove this node from the outputs of connected nodes - for (input in node.inputs) { - var inNodeOutputs = input.node.outputs; - - // Default nodes don't have outputs when exported from Blender - if (input.from < inNodeOutputs.length) { - for (outNode in inNodeOutputs[input.from]) { - if (outNode == node) { - inNodeOutputs[input.from].remove(outNode); - } - } - } - - } - - // Replace connected inputs of other nodes with default nodes - for (outputNodes in node.outputs) { - for (outNode in outputNodes) { - for (outNodeInput in outNode.inputs) { - if (outNodeInput.node == node) { - var outputIndex = outNodeInput.from; - var socketType = outputDatas[outputIndex][0]; - var socketValue = outputDatas[outputIndex][1]; - outNodeInput.node = createSocketDefaultNode(node.tree, socketType, socketValue); - } - } - } - } - + node.clearOutputs(); + node.clearInputs(); tree.nodes.remove(nodeName); } diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index 4bb0be09..4d2f0398 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -242,10 +242,7 @@ def send_event(event_id: str, opt_data: Any = None): tree_name = arm.node_utils.get_export_tree_name(node.get_tree()) node_name = arm.node_utils.get_export_node_name(node)[1:] - out_data = [(out.arm_socket_type, out.get_default_value()) for out in node.outputs] - out_data = arm.node_utils.haxe_format_socket_val(out_data) - - js = f'LivePatch.patchNodeDelete("{tree_name}", "{node_name}", {out_data});' + js = f'LivePatch.patchNodeDelete("{tree_name}", "{node_name}");' write_patch(js) elif event_id == 'ln_copy': From af520b518ee1bb54a22c66445a1762f793448947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 24 Jul 2021 13:54:48 +0200 Subject: [PATCH 230/264] Fix compatibility for frames and reroutes --- blender/arm/logicnode/replacement.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/blender/arm/logicnode/replacement.py b/blender/arm/logicnode/replacement.py index c01556a6..ac4f8632 100644 --- a/blender/arm/logicnode/replacement.py +++ b/blender/arm/logicnode/replacement.py @@ -294,6 +294,9 @@ def node_compat_sdk2108(): if not node.__class__.is_registered_node_type(): continue + if node.type in ('FRAME', 'REROUTE'): + continue + newnode = tree.nodes.new(node.__class__.bl_idname) newnode.parent = node.parent From 0456d5f080370808a7c5cd08398f368888a9d2c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 24 Jul 2021 18:21:06 +0200 Subject: [PATCH 231/264] Remove legacy code stub --- blender/arm/make.py | 3 +-- blender/arm/write_data.py | 5 +---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/blender/arm/make.py b/blender/arm/make.py index 8dc9afd2..ae63286d 100755 --- a/blender/arm/make.py +++ b/blender/arm/make.py @@ -219,8 +219,7 @@ def export_data(fp, sdk_path): # Write khafile.js enable_dce = state.is_publish and wrd.arm_dce - import_logic = not state.is_publish and arm.utils.logic_editor_space() != None - write_data.write_khafilejs(state.is_play, export_physics, export_navigation, export_ui, state.is_publish, enable_dce, ArmoryExporter.import_traits, import_logic) + write_data.write_khafilejs(state.is_play, export_physics, export_navigation, export_ui, state.is_publish, enable_dce, ArmoryExporter.import_traits) # Write Main.hx - depends on write_khafilejs for writing number of assets scene_name = arm.utils.get_project_scene_name() diff --git a/blender/arm/write_data.py b/blender/arm/write_data.py index 8e2e01ad..f43c624d 100755 --- a/blender/arm/write_data.py +++ b/blender/arm/write_data.py @@ -54,7 +54,7 @@ def remove_readonly(func, path, excinfo): def write_khafilejs(is_play, export_physics: bool, export_navigation: bool, export_ui: bool, is_publish: bool, - enable_dce: bool, import_traits: List[str], import_logicnodes) -> None: + enable_dce: bool, import_traits: List[str]) -> None: wrd = bpy.data.worlds['Arm'] sdk_path = arm.utils.get_sdk_path() @@ -160,9 +160,6 @@ project.addSources('Sources'); # if export_navigation: # khafile.write("""project.addParameter("--macro include('armory.trait.navigation')");\n""") - # if import_logicnodes: # Live patching for logic nodes - # khafile.write("""project.addParameter("--macro include('armory.logicnode')");\n""") - if not wrd.arm_compiler_inline: khafile.write("project.addParameter('--no-inline');\n") From ff5fd9c7eda4ef7b80a872f734d6880a38cc19c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 24 Jul 2021 19:49:19 +0200 Subject: [PATCH 232/264] Fix generator usage --- blender/arm/live_patch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index 4d2f0398..c6c3a466 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -224,7 +224,7 @@ def send_event(event_id: str, opt_data: Any = None): node_name = arm.node_utils.get_export_node_name(node)[1:] node_type = 'armory.logicnode.' + node.bl_idname[2:] - prop_names = (p for p in arm.node_utils.get_haxe_property_names(node)) + prop_names = tuple(arm.node_utils.get_haxe_property_names(node)) prop_values = (getattr(node, prop_name) for prop_name in prop_names) prop_datas = arm.node_utils.haxe_format_socket_val(list(zip(prop_names, prop_values))) From 0a582797567e5183d4ae9ba2ec9f2aa88f36c00c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 24 Jul 2021 19:49:35 +0200 Subject: [PATCH 233/264] Live patch: add more ignored operators --- blender/arm/live_patch.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index c6c3a466..0c4fe046 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -340,16 +340,28 @@ def on_operator(operator_id: str): # Don't re-export the scene for the following operators IGNORE_OPERATORS = ( - 'VIEW3D_OT_select', - 'VIEW3D_OT_select_box', - 'OUTLINER_OT_item_activate', - 'OBJECT_OT_editmode_toggle', + 'ARM_OT_node_add_input', + 'ARM_OT_node_add_input_output', + 'ARM_OT_node_add_input_value', + 'ARM_OT_node_add_output', + 'ARM_OT_node_call_func', + 'ARM_OT_node_remove_input', + 'ARM_OT_node_remove_input_output', + 'ARM_OT_node_remove_input_value', + 'ARM_OT_node_remove_output', + 'ARM_OT_node_search', + 'NODE_OT_delete', 'NODE_OT_duplicate_move', 'NODE_OT_hide_toggle', 'NODE_OT_link', 'NODE_OT_select', - 'NODE_OT_translate_attach_remove_on_cancel', 'NODE_OT_translate_attach', + 'NODE_OT_translate_attach_remove_on_cancel', + + 'OBJECT_OT_editmode_toggle', + 'OUTLINER_OT_item_activate', 'UI_OT_button_string_clear' + 'VIEW3D_OT_select', + 'VIEW3D_OT_select_box', ) From 568f4f3a6deab58a4e3d7d3ff63467a6ab4db554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 24 Jul 2021 19:51:01 +0200 Subject: [PATCH 234/264] Remove unnecessary privateAccess metadata --- Sources/armory/trait/internal/LivePatch.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/armory/trait/internal/LivePatch.hx b/Sources/armory/trait/internal/LivePatch.hx index b9beccbe..d4548957 100644 --- a/Sources/armory/trait/internal/LivePatch.hx +++ b/Sources/armory/trait/internal/LivePatch.hx @@ -107,7 +107,7 @@ class LivePatch extends iron.Trait { var node = tree.nodes[nodeName]; if (node == null) return; - @:privateAccess node.inputs[socketIndex].set(value); + node.inputs[socketIndex].set(value); } public static function patchNodeDelete(treeName: String, nodeName: String) { From dbb0764ca7f1a7cd84a375f8edebdfe8db7e39c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 24 Jul 2021 20:16:54 +0200 Subject: [PATCH 235/264] Fix typo --- blender/arm/live_patch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index 0c4fe046..18f2dcdc 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -361,7 +361,7 @@ IGNORE_OPERATORS = ( 'OBJECT_OT_editmode_toggle', 'OUTLINER_OT_item_activate', - 'UI_OT_button_string_clear' + 'UI_OT_button_string_clear', 'VIEW3D_OT_select', 'VIEW3D_OT_select_box', ) From 6050bceaf16fc8fb75b03d0cffe0bdc9f04a1ae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 24 Jul 2021 20:17:29 +0200 Subject: [PATCH 236/264] Fix nodes with "..._get" property names --- blender/arm/live_patch.py | 13 +++++++++---- blender/arm/make_logic.py | 6 +++--- blender/arm/node_utils.py | 7 +++++-- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index 18f2dcdc..717600c3 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -188,6 +188,11 @@ def send_event(event_id: str, opt_data: Any = None): value = arm.node_utils.haxe_format_prop_value(node, prop_name) + if prop_name.endswith('_get'): + # Hack because some nodes use a different Python property + # name than they use in Haxe + prop_name = prop_name[:-4] + js = f'LivePatch.patchUpdateNodeProp("{tree_name}", "{node_name}", "{prop_name}", {value});' write_patch(js) @@ -224,9 +229,9 @@ def send_event(event_id: str, opt_data: Any = None): node_name = arm.node_utils.get_export_node_name(node)[1:] node_type = 'armory.logicnode.' + node.bl_idname[2:] - prop_names = tuple(arm.node_utils.get_haxe_property_names(node)) - prop_values = (getattr(node, prop_name) for prop_name in prop_names) - prop_datas = arm.node_utils.haxe_format_socket_val(list(zip(prop_names, prop_values))) + prop_py_names, prop_hx_names = zip(*arm.node_utils.get_haxe_property_names(node)) + prop_values = (getattr(node, prop_name) for prop_name in prop_py_names) + prop_datas = arm.node_utils.haxe_format_socket_val(list(zip(prop_hx_names, prop_values))) inp_data = [(inp.arm_socket_type, inp.get_default_value()) for inp in node.inputs] inp_data = arm.node_utils.haxe_format_socket_val(inp_data) @@ -256,7 +261,7 @@ def send_event(event_id: str, opt_data: Any = None): newnode_name = arm.node_utils.get_export_node_name(newnode)[1:] node_name = arm.node_utils.get_export_node_name(node)[1:] - props_list = '[' + ','.join(f'"{p}"' for p in arm.node_utils.get_haxe_property_names(node)) + ']' + props_list = '[' + ','.join(f'"{p}"' for _, p in arm.node_utils.get_haxe_property_names(node)) + ']' inp_data = [(inp.arm_socket_type, inp.get_default_value()) for inp in newnode.inputs] inp_data = arm.node_utils.haxe_format_socket_val(inp_data) diff --git a/blender/arm/make_logic.py b/blender/arm/make_logic.py index 0e594f17..ad5a28ec 100755 --- a/blender/arm/make_logic.py +++ b/blender/arm/make_logic.py @@ -159,9 +159,9 @@ def build_node(node: bpy.types.Node, f: TextIO) -> Optional[str]: f.write(f'\t\tthis.nodes["{name[1:]}"] = {name};\n') # Properties - for prop_name in arm.node_utils.get_haxe_property_names(node): - prop = arm.node_utils.haxe_format_prop_value(node, prop_name) - f.write('\t\t' + name + '.' + prop_name + ' = ' + prop + ';\n') + for prop_py_name, prop_hx_name in arm.node_utils.get_haxe_property_names(node): + prop = arm.node_utils.haxe_format_prop_value(node, prop_py_name) + f.write('\t\t' + name + '.' + prop_hx_name + ' = ' + prop + ';\n') # Avoid unnecessary input/output array resizes f.write(f'\t\t{name}.preallocInputs({len(node.inputs)});\n') diff --git a/blender/arm/node_utils.py b/blender/arm/node_utils.py index 747360d5..ef465b32 100755 --- a/blender/arm/node_utils.py +++ b/blender/arm/node_utils.py @@ -110,7 +110,7 @@ def get_export_node_name(node: bpy.types.Node) -> str: return '_' + arm.utils.safesrc(node.name) -def get_haxe_property_names(node: bpy.types.Node) -> Generator[str, None, None]: +def get_haxe_property_names(node: bpy.types.Node) -> Generator[tuple[str, str], None, None]: """Generator that yields the names of all node properties that have a counterpart in the node's Haxe class. """ @@ -121,7 +121,10 @@ def get_haxe_property_names(node: bpy.types.Node) -> Generator[str, None, None]: prop_name = f'property{i}' prop_found = hasattr(node, prop_name) if prop_found: - yield prop_name + # Haxe properties are called property0 - property9 even if + # their Python equivalent can end with '_get', so yield + # both names + yield prop_name, f'property{i}' def haxe_format_socket_val(socket_val: Any, array_outer_brackets=True) -> str: From 6d2b825dd54f62eb32a9c4a6b0f6da497c7d3f45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 24 Jul 2021 20:24:04 +0200 Subject: [PATCH 237/264] Fix LN replacement and take arm_watch and arm_logic_id properties into account --- blender/arm/logicnode/replacement.py | 26 +++++++++++++------------- blender/arm/nodes_logic.py | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/blender/arm/logicnode/replacement.py b/blender/arm/logicnode/replacement.py index ac4f8632..05ab2426 100644 --- a/blender/arm/logicnode/replacement.py +++ b/blender/arm/logicnode/replacement.py @@ -129,15 +129,11 @@ def replace(tree: bpy.types.NodeTree, node: 'ArmLogicTreeNode'): if isinstance(response, arm_nodes.ArmLogicTreeNode): newnode = response # some misc. properties - newnode.parent = node.parent - newnode.location = node.location - newnode.select = node.select + copy_basic_node_props(from_node=node, to_node=newnode) elif isinstance(response, list): # a list of nodes: for newnode in response: - newnode.parent = node.parent - newnode.location = node.location - newnode.select = node.select + copy_basic_node_props(from_node=node, to_node=newnode) elif isinstance(response, NodeReplacement): replacement = response @@ -152,9 +148,7 @@ def replace(tree: bpy.types.NodeTree, node: 'ArmLogicTreeNode'): raise LookupError("The provided NodeReplacement doesn't seem to correspond to the node needing replacement") # some misc. properties - newnode.parent = node.parent - newnode.location = node.location - newnode.select = node.select + copy_basic_node_props(from_node=node, to_node=newnode) # now, use the `replacement` to hook up the new node correctly # start by applying defaults @@ -276,6 +270,15 @@ def replace_all(): bpy.ops.arm.show_node_update_errors() +def copy_basic_node_props(from_node: bpy.types.Node, to_node: bpy.types.Node): + to_node.parent = from_node.parent + to_node.location = from_node.location + to_node.select = from_node.select + + to_node.arm_logic_id = from_node.arm_logic_id + to_node.arm_watch = from_node.arm_watch + + def node_compat_sdk2108(): """SDK 21.08 broke compatibility with older nodes as nodes now use custom sockets even for Blender's default data types and custom @@ -298,10 +301,7 @@ def node_compat_sdk2108(): continue newnode = tree.nodes.new(node.__class__.bl_idname) - - newnode.parent = node.parent - newnode.location = node.location - newnode.select = node.select + copy_basic_node_props(from_node=node, to_node=newnode) # Also copy the node's version number to _not_ prevent actual node # replacement after this step diff --git a/blender/arm/nodes_logic.py b/blender/arm/nodes_logic.py index ff1c20ce..12f605fb 100755 --- a/blender/arm/nodes_logic.py +++ b/blender/arm/nodes_logic.py @@ -354,7 +354,7 @@ class ReplaceNodesOperator(bpy.types.Operator): bl_description = "Replace deprecated nodes" def execute(self, context): - arm.logicnode.replacement.replace_all(force_replacement=True) + arm.logicnode.replacement.replace_all() return {'FINISHED'} @classmethod From 8e1aa2fb0d290af49809adc98ee3b17ecf267f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 24 Jul 2021 23:06:53 +0200 Subject: [PATCH 238/264] Live patch: fix creation of nodes without properties --- blender/arm/live_patch.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index 717600c3..5b375227 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -229,7 +229,8 @@ def send_event(event_id: str, opt_data: Any = None): node_name = arm.node_utils.get_export_node_name(node)[1:] node_type = 'armory.logicnode.' + node.bl_idname[2:] - prop_py_names, prop_hx_names = zip(*arm.node_utils.get_haxe_property_names(node)) + prop_names = list(arm.node_utils.get_haxe_property_names(node)) + prop_py_names, prop_hx_names = zip(*prop_names) if len(prop_names) > 0 else ([], []) prop_values = (getattr(node, prop_name) for prop_name in prop_py_names) prop_datas = arm.node_utils.haxe_format_socket_val(list(zip(prop_hx_names, prop_values))) From cf1dd0ac712f15d8347690cf015208675e253d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 24 Jul 2021 23:07:22 +0200 Subject: [PATCH 239/264] Live patch: fix creation of node outputs --- Sources/armory/logicnode/LogicNode.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/armory/logicnode/LogicNode.hx b/Sources/armory/logicnode/LogicNode.hx index 18b3602d..6c48fde3 100644 --- a/Sources/armory/logicnode/LogicNode.hx +++ b/Sources/armory/logicnode/LogicNode.hx @@ -58,7 +58,7 @@ class LogicNode { fromNodeOuts.resize(fromIndex + 1); // Initialize with empty arrays - for (i in 0...(fromIndex - (outLen - 1))) { + for (i in outLen...fromIndex + 1) { fromNodeOuts[i] = []; } } From c52d25e471767aa37371f3abc406c1a241a57319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sun, 25 Jul 2021 00:15:32 +0200 Subject: [PATCH 240/264] arm_props.py: cleanup and fix docstrings --- blender/arm/logicnode/arm_props.py | 44 +++++++++++++++--------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/blender/arm/logicnode/arm_props.py b/blender/arm/logicnode/arm_props.py index c2777689..1a32cb28 100644 --- a/blender/arm/logicnode/arm_props.py +++ b/blender/arm/logicnode/arm_props.py @@ -15,9 +15,6 @@ from typing import Any, Callable, Sequence, Union import bpy from bpy.props import * -# Property parameter name `set` shadows built-in type `set` -__set = set - def __haxe_prop(prop_type: Callable, prop_name: str, *args, **kwargs) -> Any: """Declares a logic node property as a property that will be @@ -55,8 +52,8 @@ def HaxeBoolProperty( get=None, set=None ) -> 'bpy.types.BoolProperty': - """Declares a new BoolProperty that has a counterpart with the given - prop_name (Python and Haxe names must be identical for now). + """Declares a new BoolProperty that has a Haxe counterpart with the + given prop_name (Python and Haxe names must be identical for now). """ return __haxe_prop(BoolProperty, **locals()) @@ -76,8 +73,9 @@ def HaxeBoolVectorProperty( get=None, set=None ) -> list['bpy.types.BoolProperty']: - """Declares a new BoolVectorProperty that has a counterpart with the - given prop_name (Python and Haxe names must be identical for now). + """Declares a new BoolVectorProperty that has a Haxe counterpart + with the given prop_name (Python and Haxe names must be identical + for now). """ return __haxe_prop(BoolVectorProperty, **locals()) @@ -92,8 +90,9 @@ def HaxeCollectionProperty( override: set = set(), tags: set = set() ) -> 'bpy.types.CollectionProperty': - """Declares a new CollectionProperty that has a counterpart with the - given prop_name (Python and Haxe names must be identical for now). + """Declares a new CollectionProperty that has a Haxe counterpart + with the given prop_name (Python and Haxe names must be identical + for now). """ return __haxe_prop(CollectionProperty, **locals()) @@ -112,8 +111,8 @@ def HaxeEnumProperty( get=None, set=None ) -> 'bpy.types.EnumProperty': - """Declares a new EnumProperty that has a counterpart with the given - prop_name (Python and Haxe names must be identical for now). + """Declares a new EnumProperty that has a Haxe counterpart with the + given prop_name (Python and Haxe names must be identical for now). """ return __haxe_prop(EnumProperty, **locals()) @@ -139,7 +138,7 @@ def HaxeFloatProperty( get=None, set=None ) -> 'bpy.types.FloatProperty': - """Declares a new FloatProperty that has a counterpart with the + """Declares a new FloatProperty that has a Haxe counterpart with the given prop_name (Python and Haxe names must be identical for now). """ return __haxe_prop(FloatProperty, **locals()) @@ -167,8 +166,9 @@ def HaxeFloatVectorProperty( get=None, set=None ) -> list['bpy.types.FloatProperty']: - """Declares a new FloatVectorProperty that has a counterpart with the - given prop_name (Python and Haxe names must be identical for now). + """Declares a new FloatVectorProperty that has a Haxe counterpart + with the given prop_name (Python and Haxe names must be identical + for now). """ return __haxe_prop(FloatVectorProperty, **locals()) @@ -192,8 +192,8 @@ def HaxeIntProperty( get=None, set=None ) -> 'bpy.types.IntProperty': - """Declares a new IntProperty that has a counterpart with the given - prop_name (Python and Haxe names must be identical for now). + """Declares a new IntProperty that has a Haxe counterpart with the + given prop_name (Python and Haxe names must be identical for now). """ return __haxe_prop(IntProperty, **locals()) @@ -218,8 +218,8 @@ def HaxeIntVectorProperty( get=None, set=None ) -> list['bpy.types.IntProperty']: - """Declares a new IntVectorProperty that has a counterpart with the given - prop_name (Python and Haxe names must be identical for now). + """Declares a new IntVectorProperty that has a Haxe counterpart with + the given prop_name (Python and Haxe names must be identical for now). """ return __haxe_prop(IntVectorProperty, **locals()) @@ -236,8 +236,8 @@ def HaxePointerProperty( poll=None, update=None ) -> 'bpy.types.PointerProperty': - """Declares a new PointerProperty that has a counterpart with the - given prop_name (Python and Haxe names must be identical for now). + """Declares a new PointerProperty that has a Haxe counterpart with + the given prop_name (Python and Haxe names must be identical for now). """ return __haxe_prop(PointerProperty, **locals()) @@ -261,7 +261,7 @@ def HaxeStringProperty( get=None, set=None ) -> 'bpy.types.StringProperty': - """Declares a new StringProperty that has a counterpart with the - given prop_name (Python and Haxe names must be identical for now). + """Declares a new StringProperty that has a Haxe counterpart with + the given prop_name (Python and Haxe names must be identical for now). """ return __haxe_prop(StringProperty, **locals()) From ffee6dc521138fa25f8e896dc6765527dc56f1b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sun, 25 Jul 2021 17:02:29 +0200 Subject: [PATCH 241/264] Fix error caused by resolving merge conflict --- blender/arm/handlers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/blender/arm/handlers.py b/blender/arm/handlers.py index 24786a07..f3d03f42 100644 --- a/blender/arm/handlers.py +++ b/blender/arm/handlers.py @@ -10,6 +10,7 @@ import arm.api import arm.live_patch as live_patch import arm.logicnode.arm_nodes as arm_nodes import arm.nodes_logic +import arm.make as make import arm.make_state as state import arm.props as props import arm.utils @@ -103,7 +104,7 @@ def always() -> float: space.node_tree.arm_cached = False return 0.5 - + def poll_threads() -> float: """Polls the thread callback queue and if a thread has finished, it is joined with the main thread and the corresponding callback is @@ -120,7 +121,7 @@ def poll_threads() -> float: # Quickly check if another thread has finished return 0.01 - + appended_py_paths = [] context_screen = None From 8a758bbe267c13ecc686dcb05fda4505026a644c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sun, 25 Jul 2021 17:07:51 +0200 Subject: [PATCH 242/264] Only stop live patch session if live patch was active --- blender/arm/live_patch.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index 5b375227..9c4ce769 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -44,10 +44,11 @@ def start(): def stop(): """Stop the live patch session.""" global __running - __running = False + if __running: + __running = False - log.debug("Live patch session stopped") - bpy.msgbus.clear_by_owner(msgbus_owner) + log.debug("Live patch session stopped") + bpy.msgbus.clear_by_owner(msgbus_owner) def patch_export(): From 6a3045477f6de3df1a13feff4a5b996916ebfd7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sun, 25 Jul 2021 18:36:36 +0200 Subject: [PATCH 243/264] Fix physics node if arm_physics is false + cleanup --- .../logicnode/AddPhysicsConstraintNode.hx | 115 +++++++----------- Sources/armory/logicnode/AddRigidBodyNode.hx | 42 +++---- .../armory/logicnode/PhysicsConstraintNode.hx | 8 +- 3 files changed, 72 insertions(+), 93 deletions(-) diff --git a/Sources/armory/logicnode/AddPhysicsConstraintNode.hx b/Sources/armory/logicnode/AddPhysicsConstraintNode.hx index 4f04e2b0..3013f775 100644 --- a/Sources/armory/logicnode/AddPhysicsConstraintNode.hx +++ b/Sources/armory/logicnode/AddPhysicsConstraintNode.hx @@ -1,13 +1,11 @@ package armory.logicnode; -import armory.trait.physics.PhysicsConstraint; -#if arm_physics -import armory.trait.physics.bullet.PhysicsConstraint.ConstraintType; -import armory.trait.physics.bullet.PhysicsConstraint.ConstraintAxis; -#end import iron.object.Object; -import armory.trait.physics.RigidBody; -import armory.logicnode.PhysicsConstraintNode; + +#if arm_physics +import armory.trait.physics.PhysicsConstraint; +import armory.trait.physics.bullet.PhysicsConstraint.ConstraintType; +#end class AddPhysicsConstraintNode extends LogicNode { @@ -21,115 +19,96 @@ class AddPhysicsConstraintNode extends LogicNode { } override function run(from: Int) { - var pivotObject:Object = inputs[1].get(); + var pivotObject: Object = inputs[1].get(); rb1 = inputs[2].get(); rb2 = inputs[3].get(); - var disableCollisions: Bool = inputs[4].get(); - var breakable: Bool = inputs[5].get(); - var breakingThreshold: Float = inputs[6].get(); - var type: ConstraintType = 0; if (pivotObject == null || rb1 == null || rb2 == null) return; #if arm_physics + var disableCollisions: Bool = inputs[4].get(); + var breakable: Bool = inputs[5].get(); + var breakingThreshold: Float = inputs[6].get(); + var type: ConstraintType = 0; + var con: PhysicsConstraint = pivotObject.getTrait(PhysicsConstraint); - if(con == null) - { - switch(property0) - { - case 'Fixed': - type = Fixed; - case 'Point': - type = Point; - case 'Hinge': - type = Hinge; - case 'Slider': - type = Slider; - case 'Piston': - type = Piston; - case 'Generic Spring': - type = Generic; + if (con == null) { + switch (property0) { + case "Fixed": type = Fixed; + case "Point": type = Point; + case "Hinge": type = Hinge; + case "Slider": type = Slider; + case "Piston": type = Piston; + case "Generic Spring": type = Generic; } - if(! breakable) breakingThreshold = 0.0; + if (!breakable) breakingThreshold = 0.0; + + if (type != Generic) { - if(type != Generic) { - con = new PhysicsConstraint(rb1, rb2, type, disableCollisions, breakingThreshold); - switch (type) - { + switch (type) { case Hinge: - var setLimit:Bool = inputs[7].get(); - var low:Float = inputs[8].get(); - var up:Float = inputs[9].get(); + var setLimit: Bool = inputs[7].get(); + var low: Float = inputs[8].get(); + var up: Float = inputs[9].get(); con.setHingeConstraintLimits(setLimit, low, up); case Slider: - var setLimit:Bool = inputs[7].get(); - var low:Float = inputs[8].get(); - var up:Float = inputs[9].get(); + var setLimit: Bool = inputs[7].get(); + var low: Float = inputs[8].get(); + var up: Float = inputs[9].get(); con.setSliderConstraintLimits(setLimit, low, up); case Piston: - var setLinLimit:Bool = inputs[7].get(); - var linLow:Float = inputs[8].get(); - var linUp:Float = inputs[9].get(); - var setAngLimit:Bool = inputs[10].get(); - var angLow:Float = inputs[11].get(); - var angUp:Float = inputs[12].get(); + var setLinLimit: Bool = inputs[7].get(); + var linLow: Float = inputs[8].get(); + var linUp: Float = inputs[9].get(); + var setAngLimit: Bool = inputs[10].get(); + var angLow: Float = inputs[11].get(); + var angUp: Float = inputs[12].get(); con.setPistonConstraintLimits(setLinLimit, linLow, linUp, setAngLimit, angLow, angUp); - default: + default: } } - else - { + else { var spring: Bool = false; var prop: PhysicsConstraintNode; - for(inp in 7...inputs.length) - { + + for (inp in 7...inputs.length) { prop = inputs[inp].get(); - if(prop == null) continue; - if(prop.isSpring) - { + if (prop == null) continue; + if (prop.isSpring) { spring = true; break; } } - if(spring) { + if (spring) { con = new PhysicsConstraint(rb1, rb2, GenericSpring, disableCollisions, breakingThreshold); - } + } else { con = new PhysicsConstraint(rb1, rb2, Generic, disableCollisions, breakingThreshold); } - for(inp in 7...inputs.length) - { + for (inp in 7...inputs.length) { prop = inputs[inp].get(); - if(prop == null) continue; - (inp + ': '); + if (prop == null) continue; - if(prop.isSpring) - { + if (prop.isSpring) { con.setSpringParams(prop.isSpring, prop.value1, prop.value2, prop.axis, prop.isAngular); } - else - { + else { con.setGenericConstraintLimits(true, prop.value1, prop.value2, prop.axis, prop.isAngular); } } - - } - pivotObject.addTrait(con); - } #end - runOutput(0); } } diff --git a/Sources/armory/logicnode/AddRigidBodyNode.hx b/Sources/armory/logicnode/AddRigidBodyNode.hx index d896c2e0..601eb97d 100644 --- a/Sources/armory/logicnode/AddRigidBodyNode.hx +++ b/Sources/armory/logicnode/AddRigidBodyNode.hx @@ -1,15 +1,17 @@ package armory.logicnode; +import iron.object.Object; + #if arm_physics +import armory.trait.physics.RigidBody; import armory.trait.physics.bullet.RigidBody.Shape; #end -import iron.object.Object; -import armory.trait.physics.RigidBody; + class AddRigidBodyNode extends LogicNode { - public var property0: String;//Shape - public var property1: Bool;//Advanced + public var property0: String; //Shape + public var property1: Bool; //Advanced public var object: Object; public function new(tree: LogicTree) { @@ -18,6 +20,10 @@ class AddRigidBodyNode extends LogicNode { override function run(from: Int) { object = inputs[1].get(); + if (object == null) return; + +#if arm_physics + var mass: Float = inputs[2].get(); var active: Bool = inputs[3].get(); var animated: Bool = inputs[4].get(); @@ -50,33 +56,23 @@ class AddRigidBodyNode extends LogicNode { mask = inputs[17].get(); } - if (object == null) return; - -#if arm_physics var rb: RigidBody = object.getTrait(RigidBody); if ((group < 0) || (group > 32)) group = 1; //Limiting max groups to 32 if ((mask < 0) || (mask > 32)) mask = 1; //Limiting max masks to 32 if (rb == null) { - switch (property0){ - case "Box": - shape = Box; - case "Sphere": - shape = Sphere; - case "Capsule": - shape = Capsule; - case "Cone": - shape = Cone; - case "Cylinder": - shape = Cylinder; - case "Convex Hull": - shape = ConvexHull; - case "Mesh": - shape = Mesh; + switch (property0) { + case "Box": shape = Box; + case "Sphere": shape = Sphere; + case "Capsule": shape = Capsule; + case "Cone": shape = Cone; + case "Cylinder": shape = Cylinder; + case "Convex Hull": shape = ConvexHull; + case "Mesh": shape = Mesh; } rb = new RigidBody(shape, mass, friction, bounciness, group, mask); rb.animated = animated; - rb.staticObj = ! active; + rb.staticObj = !active; rb.isTriggerObject(trigger); if (property1) { diff --git a/Sources/armory/logicnode/PhysicsConstraintNode.hx b/Sources/armory/logicnode/PhysicsConstraintNode.hx index e6bd0bf4..a06eaa51 100644 --- a/Sources/armory/logicnode/PhysicsConstraintNode.hx +++ b/Sources/armory/logicnode/PhysicsConstraintNode.hx @@ -9,29 +9,33 @@ class PhysicsConstraintNode extends LogicNode { public var property0: String; //Linear or Angular public var property1: String; //Axis public var property2: Bool; //Is a spring + +#if arm_physics public var value1: Float; //Lower limit or Spring Stiffness public var value2: Float; //Upper limit or Spring Damping public var isAngular: Bool; public var axis: ConstraintAxis; public var isSpring: Bool; +#end public function new(tree: LogicTree) { super(tree); } override function get(from: Int): PhysicsConstraintNode { +#if arm_physics value1 = inputs[0].get(); value2 = inputs[1].get(); isAngular = property0 != "Linear"; isSpring = property2; - switch (property1){ + switch (property1) { case "X": axis = X; case "Y": axis = Y; case "Z": axis = Z; } - +#end return this; } } From 62ff11747b82bac78956c9ff270fa035df5cc5b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sun, 25 Jul 2021 19:50:51 +0200 Subject: [PATCH 244/264] Update nodes for live patch support (merge conflict cleanup) --- .../animation/LN_get_bone_fk_ik_only.py | 7 +++---- .../animation/LN_get_bone_transform.py | 7 +++---- .../animation/LN_set_bone_fk_ik_only.py | 7 +++---- .../logicnode/input/LN_get_gamepad_started.py | 7 +++---- .../logicnode/input/LN_get_input_map_key.py | 11 +++++------ .../input/LN_get_keyboard_started.py | 5 ++--- .../logicnode/input/LN_get_mouse_started.py | 5 ++--- .../arm/logicnode/input/LN_on_input_map.py | 9 ++++----- .../input/LN_remove_input_map_key.py | 9 ++++----- .../logicnode/input/LN_set_input_map_key.py | 19 +++++++++---------- blender/arm/logicnode/logic/LN_merge.py | 5 +++-- .../arm/logicnode/logic/LN_once_per_frame.py | 3 +-- blender/arm/logicnode/logic/LN_select.py | 17 ++++++++--------- .../arm/logicnode/physics/LN_get_rb_data.py | 2 +- .../LN_colorgrading_set_global_node.py | 2 +- .../LN_colorgrading_set_highlight_node.py | 2 +- .../LN_colorgrading_set_midtone_node.py | 2 +- .../LN_colorgrading_set_shadow_node.py | 2 +- .../transform/LN_get_object_location.py | 4 ++-- .../transform/LN_set_object_location.py | 4 ++-- 20 files changed, 59 insertions(+), 70 deletions(-) diff --git a/blender/arm/logicnode/animation/LN_get_bone_fk_ik_only.py b/blender/arm/logicnode/animation/LN_get_bone_fk_ik_only.py index 165b946d..21afb1ee 100644 --- a/blender/arm/logicnode/animation/LN_get_bone_fk_ik_only.py +++ b/blender/arm/logicnode/animation/LN_get_bone_fk_ik_only.py @@ -7,8 +7,7 @@ class GetBoneFkIkOnlyNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'armature' - def init(self, context): - super(GetBoneFkIkOnlyNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketString', 'Bone') - self.add_output('NodeSocketBool', 'FK or IK only') \ No newline at end of file + self.add_input('ArmStringSocket', 'Bone') + self.add_output('ArmBoolSocket', 'FK or IK only') diff --git a/blender/arm/logicnode/animation/LN_get_bone_transform.py b/blender/arm/logicnode/animation/LN_get_bone_transform.py index 0d21ea23..1ce69e7a 100644 --- a/blender/arm/logicnode/animation/LN_get_bone_transform.py +++ b/blender/arm/logicnode/animation/LN_get_bone_transform.py @@ -7,8 +7,7 @@ class GetBoneTransformNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'armature' - def init(self, context): - super(GetBoneTransformNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketString', 'Bone') - self.add_output('NodeSocketShader', 'Transform') \ No newline at end of file + self.add_input('ArmStringSocket', 'Bone') + self.add_output('ArmDynamicSocket', 'Transform') diff --git a/blender/arm/logicnode/animation/LN_set_bone_fk_ik_only.py b/blender/arm/logicnode/animation/LN_set_bone_fk_ik_only.py index 2594183a..8ed6ce45 100644 --- a/blender/arm/logicnode/animation/LN_set_bone_fk_ik_only.py +++ b/blender/arm/logicnode/animation/LN_set_bone_fk_ik_only.py @@ -7,11 +7,10 @@ class SetBoneFkIkOnlyNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'armature' - def init(self, context): - super(SetBoneFkIkOnlyNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketString', 'Bone') - self.add_input('NodeSocketBool', 'FK or IK only') + self.add_input('ArmStringSocket', 'Bone') + self.add_input('ArmBoolSocket', 'FK or IK only') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/input/LN_get_gamepad_started.py b/blender/arm/logicnode/input/LN_get_gamepad_started.py index edfbc040..08fd5fd0 100644 --- a/blender/arm/logicnode/input/LN_get_gamepad_started.py +++ b/blender/arm/logicnode/input/LN_get_gamepad_started.py @@ -6,10 +6,9 @@ class GetGamepadStartedNode(ArmLogicTreeNode): bl_label = 'Get Gamepad Started' arm_version = 1 - def init(self, context): - super(GetGamepadStartedNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketInt', 'Index') + self.add_input('ArmIntSocket', 'Index') self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketString', 'Button') + self.add_output('ArmStringSocket', 'Button') diff --git a/blender/arm/logicnode/input/LN_get_input_map_key.py b/blender/arm/logicnode/input/LN_get_input_map_key.py index 12d98935..5368a9d3 100644 --- a/blender/arm/logicnode/input/LN_get_input_map_key.py +++ b/blender/arm/logicnode/input/LN_get_input_map_key.py @@ -6,10 +6,9 @@ class GetInputMapKeyNode(ArmLogicTreeNode): bl_label = 'Get Input Map Key' arm_version = 1 - def init(self, context): - super(GetInputMapKeyNode, self).init(context) - self.add_input('NodeSocketString', 'Input Map') - self.add_input('NodeSocketString', 'Key') + def arm_init(self, context): + self.add_input('ArmStringSocket', 'Input Map') + self.add_input('ArmStringSocket', 'Key') - self.add_output('NodeSocketFloat', 'Scale', default_value = 1.0) - self.add_output('NodeSocketFloat', 'Deadzone') \ No newline at end of file + self.add_output('ArmFloatSocket', 'Scale', default_value = 1.0) + self.add_output('ArmFloatSocket', 'Deadzone') diff --git a/blender/arm/logicnode/input/LN_get_keyboard_started.py b/blender/arm/logicnode/input/LN_get_keyboard_started.py index 0f655b04..1810c6e0 100644 --- a/blender/arm/logicnode/input/LN_get_keyboard_started.py +++ b/blender/arm/logicnode/input/LN_get_keyboard_started.py @@ -6,9 +6,8 @@ class GetKeyboardStartedNode(ArmLogicTreeNode): bl_label = 'Get Keyboard Started' arm_version = 1 - def init(self, context): - super(GetKeyboardStartedNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketString', 'Key') + self.add_output('ArmStringSocket', 'Key') diff --git a/blender/arm/logicnode/input/LN_get_mouse_started.py b/blender/arm/logicnode/input/LN_get_mouse_started.py index 368aa433..9a6fce1f 100644 --- a/blender/arm/logicnode/input/LN_get_mouse_started.py +++ b/blender/arm/logicnode/input/LN_get_mouse_started.py @@ -6,9 +6,8 @@ class GetMouseStartedNode(ArmLogicTreeNode): bl_label = 'Get Mouse Started' arm_version = 1 - def init(self, context): - super(GetMouseStartedNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketString', 'Button') + self.add_output('ArmStringSocket', 'Button') diff --git a/blender/arm/logicnode/input/LN_on_input_map.py b/blender/arm/logicnode/input/LN_on_input_map.py index 0cc540f5..c851eefe 100644 --- a/blender/arm/logicnode/input/LN_on_input_map.py +++ b/blender/arm/logicnode/input/LN_on_input_map.py @@ -6,11 +6,10 @@ class OnInputMapNode(ArmLogicTreeNode): bl_label = 'On Input Map' arm_version = 1 - def init(self, context): - super(OnInputMapNode, self).init(context) - self.add_input('NodeSocketString', 'Input Map') + def arm_init(self, context): + self.add_input('ArmStringSocket', 'Input Map') self.add_output('ArmNodeSocketAction', 'Started') self.add_output('ArmNodeSocketAction', 'Released') - self.add_output('NodeSocketFloat', 'Value') - self.add_output('NodeSocketString', 'Key Pressed') \ No newline at end of file + self.add_output('ArmFloatSocket', 'Value') + self.add_output('ArmStringSocket', 'Key Pressed') diff --git a/blender/arm/logicnode/input/LN_remove_input_map_key.py b/blender/arm/logicnode/input/LN_remove_input_map_key.py index 8821ef05..8da8562b 100644 --- a/blender/arm/logicnode/input/LN_remove_input_map_key.py +++ b/blender/arm/logicnode/input/LN_remove_input_map_key.py @@ -6,10 +6,9 @@ class RemoveInputMapKeyNode(ArmLogicTreeNode): bl_label = 'Remove Input Map Key' arm_version = 1 - def init(self, context): - super(RemoveInputMapKeyNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Input Map') - self.add_input('NodeSocketString', 'Key') + self.add_input('ArmStringSocket', 'Input Map') + self.add_input('ArmStringSocket', 'Key') - self.add_output('ArmNodeSocketAction', 'Out') \ No newline at end of file + self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/input/LN_set_input_map_key.py b/blender/arm/logicnode/input/LN_set_input_map_key.py index a2157745..75997d7d 100644 --- a/blender/arm/logicnode/input/LN_set_input_map_key.py +++ b/blender/arm/logicnode/input/LN_set_input_map_key.py @@ -6,23 +6,22 @@ class SetInputMapKeyNode(ArmLogicTreeNode): bl_label = 'Set Input Map Key' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('keyboard', 'Keyboard', 'Keyboard input'), ('mouse', 'Mouse', 'Mouse input'), ('gamepad', 'Gamepad', 'Gamepad input')], name='', default='keyboard') - def init(self, context): - super(SetInputMapKeyNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Input Map') - self.add_input('NodeSocketString', 'Key') - self.add_input('NodeSocketFloat', 'Scale', default_value=1.0) - self.add_input('NodeSocketFloat', 'Deadzone') - self.add_input('NodeSocketInt', 'Index') + self.add_input('ArmStringSocket', 'Input Map') + self.add_input('ArmStringSocket', 'Key') + self.add_input('ArmFloatSocket', 'Scale', default_value=1.0) + self.add_input('ArmFloatSocket', 'Deadzone') + self.add_input('ArmIntSocket', 'Index') self.add_output('ArmNodeSocketAction', 'Out') - def draw_buttons(self, context, layout): - layout.prop(self, 'property0') \ No newline at end of file + layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/logic/LN_merge.py b/blender/arm/logicnode/logic/LN_merge.py index 5f6b1f73..a209c690 100644 --- a/blender/arm/logicnode/logic/LN_merge.py +++ b/blender/arm/logicnode/logic/LN_merge.py @@ -29,7 +29,8 @@ class MergeNode(ArmLogicTreeNode): def update_exec_mode(self, context): self.outputs['Active Input Index'].hide = self.property0 == 'once_per_frame' - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', name='Execution Mode', description='The node\'s behaviour if multiple inputs are active on the same frame', items=[('once_per_input', 'Once Per Input', @@ -47,7 +48,7 @@ class MergeNode(ArmLogicTreeNode): def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketInt', 'Active Input Index') + self.add_output('ArmIntSocket', 'Active Input Index') def draw_buttons(self, context, layout): layout.prop(self, 'property0', text='') diff --git a/blender/arm/logicnode/logic/LN_once_per_frame.py b/blender/arm/logicnode/logic/LN_once_per_frame.py index b7dba2f1..56c82fc5 100644 --- a/blender/arm/logicnode/logic/LN_once_per_frame.py +++ b/blender/arm/logicnode/logic/LN_once_per_frame.py @@ -9,8 +9,7 @@ class OncePerFrameNode(ArmLogicTreeNode): arm_section = 'flow' arm_version = 1 - def init(self, context): - super(OncePerFrameNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/logic/LN_select.py b/blender/arm/logicnode/logic/LN_select.py index 4ea3762a..55f39a83 100644 --- a/blender/arm/logicnode/logic/LN_select.py +++ b/blender/arm/logicnode/logic/LN_select.py @@ -37,7 +37,8 @@ class SelectNode(ArmLogicTreeNode): def update_exec_mode(self, context): self.set_mode() - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', name='Execution Mode', description="The node's behaviour.", items=[ @@ -55,9 +56,7 @@ class SelectNode(ArmLogicTreeNode): super().__init__() array_nodes[str(id(self))] = self - def init(self, context): - super().init(context) - + def arm_init(self, context): self.set_mode() def set_mode(self): @@ -65,8 +64,8 @@ class SelectNode(ArmLogicTreeNode): self.outputs.clear() if self.property0 == 'from_index': - self.add_input('NodeSocketInt', 'Index') - self.add_input('NodeSocketShader', 'Default') + self.add_input('ArmIntSocket', 'Index') + self.add_input('ArmDynamicSocket', 'Default') self.num_choices = 0 # from_input @@ -75,12 +74,12 @@ class SelectNode(ArmLogicTreeNode): # 0 for the "from_index" mode and it makes the code simpler # if we stick to the same convention for both exec modes self.add_input('ArmNodeSocketAction', 'Input 0') - self.add_input('NodeSocketShader', 'Value 0') + self.add_input('ArmDynamicSocket', 'Value 0') self.num_choices = 1 self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketShader', 'Value') + self.add_output('ArmDynamicSocket', 'Value') def draw_buttons(self, context, layout): layout.prop(self, 'property0', text='') @@ -102,7 +101,7 @@ class SelectNode(ArmLogicTreeNode): # Move new action input up to the end of all other action inputs self.inputs.move(from_index=len(self.inputs) - 1, to_index=self.num_choices) - self.add_input('NodeSocketShader', f'Value {self.num_choices}') + self.add_input('ArmDynamicSocket', f'Value {self.num_choices}') self.num_choices += 1 diff --git a/blender/arm/logicnode/physics/LN_get_rb_data.py b/blender/arm/logicnode/physics/LN_get_rb_data.py index 3671516f..76ecb2e2 100644 --- a/blender/arm/logicnode/physics/LN_get_rb_data.py +++ b/blender/arm/logicnode/physics/LN_get_rb_data.py @@ -7,7 +7,7 @@ class GetRigidBodyDataNode(ArmLogicTreeNode): arm_section = 'props' arm_version = 1 - def init(self, context): + def arm_init(self, context): self.inputs.new('ArmNodeSocketObject', 'Object') self.outputs.new('ArmBoolSocket', 'Is RB') diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_set_global_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_set_global_node.py index c4746472..a72ac52a 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_set_global_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_set_global_node.py @@ -37,7 +37,7 @@ class ColorgradingSetGlobalNode(ArmLogicTreeNode): items = [('RGB', 'RGB', 'RGB'), ('Uniform', 'Uniform', 'Uniform')], name='Mode', default='Uniform', update=update_node) - property1 : StringProperty(name="Loaded Data", description="Loaded data - Just ignore", default="") + property1 : HaxeStringProperty('property1', name="Loaded Data", description="Loaded data - Just ignore", default="") filepath : StringProperty(name="Preset File", description="Postprocess colorgrading preset file", default="", subtype="FILE_PATH", update=set_data) diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_set_highlight_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_set_highlight_node.py index f8a801cb..01b23cd1 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_set_highlight_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_set_highlight_node.py @@ -37,7 +37,7 @@ class ColorgradingSetHighlightNode(ArmLogicTreeNode): items = [('RGB', 'RGB', 'RGB'), ('Uniform', 'Uniform', 'Uniform')], name='Mode', default='Uniform', update=update_node) - property1 : StringProperty(name="Loaded Data", description="Loaded data - Just ignore", default="") + property1 : HaxeStringProperty('property1', name="Loaded Data", description="Loaded data - Just ignore", default="") filepath : StringProperty(name="Preset File", description="Postprocess colorgrading preset file", default="", subtype="FILE_PATH", update=set_data) diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_set_midtone_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_set_midtone_node.py index 8d8379cb..8a5bd19c 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_set_midtone_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_set_midtone_node.py @@ -37,7 +37,7 @@ class ColorgradingSetMidtoneNode(ArmLogicTreeNode): items = [('RGB', 'RGB', 'RGB'), ('Uniform', 'Uniform', 'Uniform')], name='Mode', default='Uniform', update=update_node) - property1 : StringProperty(name="Loaded Data", description="Loaded data - Just ignore", default="") + property1 : HaxeStringProperty('property1', name="Loaded Data", description="Loaded data - Just ignore", default="") filepath : StringProperty(name="Preset File", description="Postprocess colorgrading preset file", default="", subtype="FILE_PATH", update=set_data) diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_set_shadow_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_set_shadow_node.py index 84fa8956..bc05651d 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_set_shadow_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_set_shadow_node.py @@ -37,7 +37,7 @@ class ColorgradingSetShadowNode(ArmLogicTreeNode): items = [('RGB', 'RGB', 'RGB'), ('Uniform', 'Uniform', 'Uniform')], name='Mode', default='Uniform', update=update_node) - property1 : StringProperty(name="Loaded Data", description="Loaded data - Just ignore", default="") + property1 : HaxeStringProperty('property1', name="Loaded Data", description="Loaded data - Just ignore", default="") filepath : StringProperty(name="Preset File", description="Postprocess colorgrading preset file", default="", subtype="FILE_PATH", update=set_data) diff --git a/blender/arm/logicnode/transform/LN_get_object_location.py b/blender/arm/logicnode/transform/LN_get_object_location.py index 0f12aa5b..d4af86b4 100644 --- a/blender/arm/logicnode/transform/LN_get_object_location.py +++ b/blender/arm/logicnode/transform/LN_get_object_location.py @@ -2,7 +2,7 @@ from arm.logicnode.arm_nodes import * class GetLocationNode(ArmLogicTreeNode): """Get the location of the given object in world coordinates. - + @input Parent Relative: If enabled, transforms the world coordinates into object parent local coordinates @seeNode Set Object Location @@ -16,7 +16,7 @@ class GetLocationNode(ArmLogicTreeNode): def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketBool', 'Parent Relative') + self.add_input('ArmBoolSocket', 'Parent Relative') self.add_output('ArmVectorSocket', 'Location') diff --git a/blender/arm/logicnode/transform/LN_set_object_location.py b/blender/arm/logicnode/transform/LN_set_object_location.py index 5ee43596..5c9e4ac9 100644 --- a/blender/arm/logicnode/transform/LN_set_object_location.py +++ b/blender/arm/logicnode/transform/LN_set_object_location.py @@ -18,11 +18,11 @@ class SetLocationNode(ArmLogicTreeNode): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmVectorSocket', 'Location') - self.add_input('NodeSocketBool', 'Parent Relative') + self.add_input('ArmBoolSocket', 'Parent Relative') self.add_output('ArmNodeSocketAction', 'Out') def get_replacement_node(self, node_tree: bpy.types.NodeTree): if self.arm_version not in (0, 1): raise LookupError() - return NodeReplacement.Identity(self) \ No newline at end of file + return NodeReplacement.Identity(self) From 549040fc093c5b92ea23630d5f41ed3e21a456b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sun, 25 Jul 2021 20:13:47 +0200 Subject: [PATCH 245/264] Fix publishing with live patch enabled --- blender/arm/exporter.py | 4 ++-- blender/arm/make_logic.py | 14 ++++++++------ blender/arm/utils.py | 17 +++++++++++++---- blender/arm/write_data.py | 6 +++--- 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/blender/arm/exporter.py b/blender/arm/exporter.py index ff740963..10d23e13 100755 --- a/blender/arm/exporter.py +++ b/blender/arm/exporter.py @@ -2454,7 +2454,7 @@ Make sure the mesh only has tris/quads.""") else: self.material_to_object_dict[mat] = [bobject] self.material_to_arm_object_dict[mat] = [o] - + # Add UniformsManager trait if type is NodeType.MESH: uniformManager = {} @@ -2672,7 +2672,7 @@ Make sure the mesh only has tris/quads.""") } self.output['traits'].append(out_trait) - if wrd.arm_live_patch: + if arm.utils.is_livepatch_enabled(): if 'traits' not in self.output: self.output['traits'] = [] out_trait = {'type': 'Script', 'class_name': 'armory.trait.internal.LivePatch'} diff --git a/blender/arm/make_logic.py b/blender/arm/make_logic.py index ad5a28ec..0e74d043 100755 --- a/blender/arm/make_logic.py +++ b/blender/arm/make_logic.py @@ -78,7 +78,7 @@ def build_node_tree(node_group: 'arm.nodes_logic.ArmLogicTree'): f.write('\t\tname = "' + group_name + '";\n') f.write('\t\tthis.functionNodes = new Map();\n') f.write('\t\tthis.functionOutputNodes = new Map();\n') - if wrd.arm_live_patch: + if arm.utils.is_livepatch_enabled(): f.write(f'\t\tarmory.logicnode.LogicTree.nodeTrees["{group_name}"] = this;\n') f.write('\t\tnotifyOnAdd(add);\n') f.write('\t}\n\n') @@ -113,6 +113,8 @@ def build_node(node: bpy.types.Node, f: TextIO) -> Optional[str]: global parsed_nodes global parsed_ids + use_live_patch = arm.utils.is_livepatch_enabled() + if node.type == 'REROUTE': if len(node.inputs) > 0 and len(node.inputs[0].links) > 0: return build_node(node.inputs[0].links[0].from_node, f) @@ -154,7 +156,7 @@ def build_node(node: bpy.types.Node, f: TextIO) -> Optional[str]: f.write('\t\t' + name + '.name = "' + name[1:] + '";\n') f.write('\t\t' + name + '.watch(true);\n') - elif wrd.arm_live_patch: + elif use_live_patch: f.write('\t\t' + name + '.name = "' + name[1:] + '";\n') f.write(f'\t\tthis.nodes["{name[1:]}"] = {name};\n') @@ -212,8 +214,8 @@ def build_node(node: bpy.types.Node, f: TextIO) -> Optional[str]: from_type = inp.arm_socket_type # Add input - f.write(f'\t\t{"var __link = " if wrd.arm_live_patch else ""}armory.logicnode.LogicNode.addLink({inp_name}, {name}, {inp_from}, {idx});\n') - if wrd.arm_live_patch: + f.write(f'\t\t{"var __link = " if use_live_patch else ""}armory.logicnode.LogicNode.addLink({inp_name}, {name}, {inp_from}, {idx});\n') + if use_live_patch: to_type = inp.arm_socket_type f.write(f'\t\t__link.fromType = "{from_type}";') f.write(f'\t\t__link.toType = "{to_type}";') @@ -224,8 +226,8 @@ def build_node(node: bpy.types.Node, f: TextIO) -> Optional[str]: # Linked outputs are already handled after iterating over inputs # above, so only unconnected outputs are handled here if not out.is_linked: - f.write(f'\t\t{"var __link = " if wrd.arm_live_patch else ""}armory.logicnode.LogicNode.addLink({name}, {build_default_node(out)}, {idx}, 0);\n') - if wrd.arm_live_patch: + f.write(f'\t\t{"var __link = " if use_live_patch else ""}armory.logicnode.LogicNode.addLink({name}, {build_default_node(out)}, {idx}, 0);\n') + if use_live_patch: out_type = out.arm_socket_type f.write(f'\t\t__link.fromType = "{out_type}";') f.write(f'\t\t__link.toType = "{out_type}";') diff --git a/blender/arm/utils.py b/blender/arm/utils.py index 52d836a0..dc468a8d 100755 --- a/blender/arm/utils.py +++ b/blender/arm/utils.py @@ -78,6 +78,15 @@ def convert_image(image, path, file_format='JPEG'): ren.image_settings.file_format = orig_file_format ren.image_settings.color_mode = orig_color_mode + +def is_livepatch_enabled(): + """Returns whether live patch is enabled and can be used.""" + wrd = bpy.data.worlds['Arm'] + # If the game is published, the target is krom-[OS] and not krom, + # so there is no live patch when publishing + return wrd.arm_live_patch and state.target == 'krom' + + def blend_name(): return bpy.path.basename(bpy.context.blend_data.filepath).rsplit('.', 1)[0] @@ -981,11 +990,11 @@ def get_visual_studio_from_version(version: str) -> str: def get_list_installed_vs(get_version: bool, get_name: bool, get_path: bool) -> []: err = '' items = [] - path_file = os.path.join(get_sdk_path(), 'Kha', 'Kinc', 'Tools', 'kincmake', 'Data', 'windows', 'vswhere.exe') + path_file = os.path.join(get_sdk_path(), 'Kha', 'Kinc', 'Tools', 'kincmake', 'Data', 'windows', 'vswhere.exe') if not os.path.isfile(path_file): err = 'File "'+ path_file +'" not found.' return items, err - + if (not get_version) and (not get_name) and (not get_path): return items, err @@ -1011,8 +1020,8 @@ def get_list_installed_vs(get_version: bool, get_name: bool, get_path: bool) -> return items, err for i in range(count_items): - v = items_ver[i][0] if len(items_ver) > i else '' - v_full = items_ver[i][1] if len(items_ver) > i else '' + v = items_ver[i][0] if len(items_ver) > i else '' + v_full = items_ver[i][1] if len(items_ver) > i else '' n = items_name[i] if len(items_name) > i else '' p = items_path[i] if len(items_path) > i else '' items.append((v, n, p, v_full)) diff --git a/blender/arm/write_data.py b/blender/arm/write_data.py index f43c624d..0eb56dc9 100755 --- a/blender/arm/write_data.py +++ b/blender/arm/write_data.py @@ -166,10 +166,10 @@ project.addSources('Sources'); if enable_dce: khafile.write("project.addParameter('-dce full');\n") - live_patch = wrd.arm_live_patch and state.target == 'krom' - if wrd.arm_debug_console or live_patch: + use_live_patch = arm.utils.is_livepatch_enabled() + if wrd.arm_debug_console or use_live_patch: import_traits.append('armory.trait.internal.Bridge') - if live_patch: + if use_live_patch: assets.add_khafile_def('arm_patch') # Include all logic node classes so that they can later # get instantiated From ee208ece183f35ac226db8fe441f109f83cf4ab5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sun, 25 Jul 2021 20:20:43 +0200 Subject: [PATCH 246/264] Live patch: ignore two more operators --- blender/arm/live_patch.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index 9c4ce769..b2bf81a8 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -362,6 +362,7 @@ IGNORE_OPERATORS = ( 'NODE_OT_duplicate_move', 'NODE_OT_hide_toggle', 'NODE_OT_link', + 'NODE_OT_move_detach_links', 'NODE_OT_select', 'NODE_OT_translate_attach', 'NODE_OT_translate_attach_remove_on_cancel', @@ -369,6 +370,7 @@ IGNORE_OPERATORS = ( 'OBJECT_OT_editmode_toggle', 'OUTLINER_OT_item_activate', 'UI_OT_button_string_clear', + 'UI_OT_eyedropper_id', 'VIEW3D_OT_select', 'VIEW3D_OT_select_box', ) From bce14549ac143169c8ab12ae5719d11a6b0bd8fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Mon, 26 Jul 2021 00:02:54 +0200 Subject: [PATCH 247/264] Fix LN live patch when using multiple instances of the same logic tree --- Sources/armory/logicnode/LogicTree.hx | 5 +- Sources/armory/trait/internal/LivePatch.hx | 204 +++++++++++---------- blender/arm/make_logic.py | 21 ++- 3 files changed, 127 insertions(+), 103 deletions(-) diff --git a/Sources/armory/logicnode/LogicTree.hx b/Sources/armory/logicnode/LogicTree.hx index 22daf13a..ed06b08e 100644 --- a/Sources/armory/logicnode/LogicTree.hx +++ b/Sources/armory/logicnode/LogicTree.hx @@ -3,7 +3,10 @@ package armory.logicnode; class LogicTree extends iron.Trait { #if arm_patch - public static var nodeTrees = new Map(); + /** + Stores all trait instances of the tree via its name. + **/ + public static var nodeTrees = new Map>(); /** [node name => logic node] for later node replacement for live patching. diff --git a/Sources/armory/trait/internal/LivePatch.hx b/Sources/armory/trait/internal/LivePatch.hx index d4548957..743f66be 100644 --- a/Sources/armory/trait/internal/LivePatch.hx +++ b/Sources/armory/trait/internal/LivePatch.hx @@ -33,147 +33,161 @@ class LivePatch extends iron.Trait { } public static function patchCreateNodeLink(treeName: String, fromNodeName: String, toNodeName: String, fromIndex: Int, toIndex: Int) { - var tree = LogicTree.nodeTrees[treeName]; - if (tree == null) return; + if (!LogicTree.nodeTrees.exists(treeName)) return; + var trees = LogicTree.nodeTrees[treeName]; - var fromNode = tree.nodes[fromNodeName]; - var toNode = tree.nodes[toNodeName]; - if (fromNode == null || toNode == null) return; + for (tree in trees) { + var fromNode = tree.nodes[fromNodeName]; + var toNode = tree.nodes[toNodeName]; + if (fromNode == null || toNode == null) return; - LogicNode.addLink(fromNode, toNode, fromIndex, toIndex); + LogicNode.addLink(fromNode, toNode, fromIndex, toIndex); + } } public static function patchSetNodeLinks(treeName: String, nodeName: String, inputDatas: Array, outputDatas: Array>) { - var tree = LogicTree.nodeTrees[treeName]; - if (tree == null) return; + if (!LogicTree.nodeTrees.exists(treeName)) return; + var trees = LogicTree.nodeTrees[treeName]; - var node = tree.nodes[nodeName]; - if (node == null) return; + for (tree in trees) { + var node = tree.nodes[nodeName]; + if (node == null) return; - node.clearInputs(); - node.clearOutputs(); + node.clearInputs(); + node.clearOutputs(); - for (inputData in inputDatas) { - var fromNode: LogicNode; - var fromIndex: Int; + for (inputData in inputDatas) { + var fromNode: LogicNode; + var fromIndex: Int; - if (inputData.isLinked) { - fromNode = tree.nodes[inputData.fromNode]; - if (fromNode == null) continue; - fromIndex = inputData.fromIndex; - } - else { - fromNode = LogicNode.createSocketDefaultNode(node.tree, inputData.socketType, inputData.socketValue); - fromIndex = 0; - } - - LogicNode.addLink(fromNode, node, fromIndex, inputData.toIndex); - } - - for (outputData in outputDatas) { - for (linkData in outputData) { - var toNode: LogicNode; - var toIndex: Int; - - if (linkData.isLinked) { - toNode = tree.nodes[linkData.toNode]; - if (toNode == null) continue; - toIndex = linkData.toIndex; + if (inputData.isLinked) { + fromNode = tree.nodes[inputData.fromNode]; + if (fromNode == null) continue; + fromIndex = inputData.fromIndex; } else { - toNode = LogicNode.createSocketDefaultNode(node.tree, linkData.socketType, linkData.socketValue); - toIndex = 0; + fromNode = LogicNode.createSocketDefaultNode(node.tree, inputData.socketType, inputData.socketValue); + fromIndex = 0; } - LogicNode.addLink(node, toNode, linkData.fromIndex, toIndex); + LogicNode.addLink(fromNode, node, fromIndex, inputData.toIndex); + } + + for (outputData in outputDatas) { + for (linkData in outputData) { + var toNode: LogicNode; + var toIndex: Int; + + if (linkData.isLinked) { + toNode = tree.nodes[linkData.toNode]; + if (toNode == null) continue; + toIndex = linkData.toIndex; + } + else { + toNode = LogicNode.createSocketDefaultNode(node.tree, linkData.socketType, linkData.socketValue); + toIndex = 0; + } + + LogicNode.addLink(node, toNode, linkData.fromIndex, toIndex); + } } } } public static function patchUpdateNodeProp(treeName: String, nodeName: String, propName: String, value: Dynamic) { - var tree = LogicTree.nodeTrees[treeName]; - if (tree == null) return; + if (!LogicTree.nodeTrees.exists(treeName)) return; + var trees = LogicTree.nodeTrees[treeName]; - var node = tree.nodes[nodeName]; - if (node == null) return; + for (tree in trees) { + var node = tree.nodes[nodeName]; + if (node == null) return; - Reflect.setField(node, propName, value); + Reflect.setField(node, propName, value); + } } public static function patchUpdateNodeInputVal(treeName: String, nodeName: String, socketIndex: Int, value: Dynamic) { - var tree = LogicTree.nodeTrees[treeName]; - if (tree == null) return; + if (!LogicTree.nodeTrees.exists(treeName)) return; + var trees = LogicTree.nodeTrees[treeName]; - var node = tree.nodes[nodeName]; - if (node == null) return; + for (tree in trees) { + var node = tree.nodes[nodeName]; + if (node == null) return; - node.inputs[socketIndex].set(value); + node.inputs[socketIndex].set(value); + } } public static function patchNodeDelete(treeName: String, nodeName: String) { - var tree = LogicTree.nodeTrees[treeName]; - if (tree == null) return; + if (!LogicTree.nodeTrees.exists(treeName)) return; + var trees = LogicTree.nodeTrees[treeName]; - var node = tree.nodes[nodeName]; - if (node == null) return; + for (tree in trees) { + var node = tree.nodes[nodeName]; + if (node == null) return; - node.clearOutputs(); - node.clearInputs(); - tree.nodes.remove(nodeName); + node.clearOutputs(); + node.clearInputs(); + tree.nodes.remove(nodeName); + } } public static function patchNodeCreate(treeName: String, nodeName: String, nodeType: String, propDatas: Array>, inputDatas: Array>, outputDatas: Array>) { - var tree = LogicTree.nodeTrees[treeName]; - if (tree == null) return; + if (!LogicTree.nodeTrees.exists(treeName)) return; + var trees = LogicTree.nodeTrees[treeName]; - // No further constructor parameters required here, all variable nodes - // use optional further parameters and all values are set later in this - // function. - var newNode: LogicNode = Type.createInstance(Type.resolveClass(nodeType), [tree]); - newNode.name = nodeName; - tree.nodes[nodeName] = newNode; + for (tree in trees) { + // No further constructor parameters required here, all variable nodes + // use optional further parameters and all values are set later in this + // function. + var newNode: LogicNode = Type.createInstance(Type.resolveClass(nodeType), [tree]); + newNode.name = nodeName; + tree.nodes[nodeName] = newNode; - for (propData in propDatas) { - Reflect.setField(newNode, propData[0], propData[1]); - } + for (propData in propDatas) { + Reflect.setField(newNode, propData[0], propData[1]); + } - var i = 0; - for (inputData in inputDatas) { - LogicNode.addLink(LogicNode.createSocketDefaultNode(newNode.tree, inputData[0], inputData[1]), newNode, 0, i++); - } + var i = 0; + for (inputData in inputDatas) { + LogicNode.addLink(LogicNode.createSocketDefaultNode(newNode.tree, inputData[0], inputData[1]), newNode, 0, i++); + } - i = 0; - for (outputData in outputDatas) { - LogicNode.addLink(newNode, LogicNode.createSocketDefaultNode(newNode.tree, outputData[0], outputData[1]), i++, 0); + i = 0; + for (outputData in outputDatas) { + LogicNode.addLink(newNode, LogicNode.createSocketDefaultNode(newNode.tree, outputData[0], outputData[1]), i++, 0); + } } } public static function patchNodeCopy(treeName: String, nodeName: String, newNodeName: String, copyProps: Array, inputDatas: Array>, outputDatas: Array>) { - var tree = LogicTree.nodeTrees[treeName]; - if (tree == null) return; + if (!LogicTree.nodeTrees.exists(treeName)) return; + var trees = LogicTree.nodeTrees[treeName]; - var node = tree.nodes[nodeName]; - if (node == null) return; + for (tree in trees) { + var node = tree.nodes[nodeName]; + if (node == null) return; - // No further constructor parameters required here, all variable nodes - // use optional further parameters and all values are set later in this - // function. - var newNode: LogicNode = Type.createInstance(Type.getClass(node), [tree]); - newNode.name = newNodeName; - tree.nodes[newNodeName] = newNode; + // No further constructor parameters required here, all variable nodes + // use optional further parameters and all values are set later in this + // function. + var newNode: LogicNode = Type.createInstance(Type.getClass(node), [tree]); + newNode.name = newNodeName; + tree.nodes[newNodeName] = newNode; - for (propName in copyProps) { - Reflect.setField(newNode, propName, Reflect.field(node, propName)); - } + for (propName in copyProps) { + Reflect.setField(newNode, propName, Reflect.field(node, propName)); + } - var i = 0; - for (inputData in inputDatas) { - LogicNode.addLink(LogicNode.createSocketDefaultNode(newNode.tree, inputData[0], inputData[1]), newNode, 0, i++); - } + var i = 0; + for (inputData in inputDatas) { + LogicNode.addLink(LogicNode.createSocketDefaultNode(newNode.tree, inputData[0], inputData[1]), newNode, 0, i++); + } - i = 0; - for (outputData in outputDatas) { - LogicNode.addLink(newNode, LogicNode.createSocketDefaultNode(newNode.tree, outputData[0], outputData[1]), i++, 0); + i = 0; + for (outputData in outputDatas) { + LogicNode.addLink(newNode, LogicNode.createSocketDefaultNode(newNode.tree, outputData[0], outputData[1]), i++, 0); + } } } diff --git a/blender/arm/make_logic.py b/blender/arm/make_logic.py index 0e74d043..5ecf42a1 100755 --- a/blender/arm/make_logic.py +++ b/blender/arm/make_logic.py @@ -79,7 +79,14 @@ def build_node_tree(node_group: 'arm.nodes_logic.ArmLogicTree'): f.write('\t\tthis.functionNodes = new Map();\n') f.write('\t\tthis.functionOutputNodes = new Map();\n') if arm.utils.is_livepatch_enabled(): - f.write(f'\t\tarmory.logicnode.LogicTree.nodeTrees["{group_name}"] = this;\n') + # Store a reference to this trait instance in Logictree.nodeTrees + f.write('\t\tvar nodeTrees = armory.logicnode.LogicTree.nodeTrees;\n') + f.write(f'\t\tif (nodeTrees.exists("{group_name}")) ' + '{\n') + f.write(f'\t\t\tnodeTrees["{group_name}"].push(this);\n') + f.write('\t\t} else {\n') + f.write(f'\t\t\tnodeTrees["{group_name}"] = cast [this];\n') + f.write('\t\t}\n') + f.write('\t\tnotifyOnRemove(() -> { nodeTrees.remove("' + group_name + '"); });\n') f.write('\t\tnotifyOnAdd(add);\n') f.write('\t}\n\n') f.write('\toverride public function add() {\n') @@ -217,9 +224,9 @@ def build_node(node: bpy.types.Node, f: TextIO) -> Optional[str]: f.write(f'\t\t{"var __link = " if use_live_patch else ""}armory.logicnode.LogicNode.addLink({inp_name}, {name}, {inp_from}, {idx});\n') if use_live_patch: to_type = inp.arm_socket_type - f.write(f'\t\t__link.fromType = "{from_type}";') - f.write(f'\t\t__link.toType = "{to_type}";') - f.write(f'\t\t__link.toValue = {arm.node_utils.haxe_format_socket_val(inp.get_default_value())};') + f.write(f'\t\t__link.fromType = "{from_type}";\n') + f.write(f'\t\t__link.toType = "{to_type}";\n') + f.write(f'\t\t__link.toValue = {arm.node_utils.haxe_format_socket_val(inp.get_default_value())};\n') # Create outputs for idx, out in enumerate(node.outputs): @@ -229,9 +236,9 @@ def build_node(node: bpy.types.Node, f: TextIO) -> Optional[str]: f.write(f'\t\t{"var __link = " if use_live_patch else ""}armory.logicnode.LogicNode.addLink({name}, {build_default_node(out)}, {idx}, 0);\n') if use_live_patch: out_type = out.arm_socket_type - f.write(f'\t\t__link.fromType = "{out_type}";') - f.write(f'\t\t__link.toType = "{out_type}";') - f.write(f'\t\t__link.toValue = {arm.node_utils.haxe_format_socket_val(out.get_default_value())};') + f.write(f'\t\t__link.fromType = "{out_type}";\n') + f.write(f'\t\t__link.toType = "{out_type}";\n') + f.write(f'\t\t__link.toValue = {arm.node_utils.haxe_format_socket_val(out.get_default_value())};\n') return name From 3bad8786508d584e47ff5b8c9d3e255770184caa Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Mon, 26 Jul 2021 13:48:44 +0200 Subject: [PATCH 248/264] Add exception for null data --- Sources/armory/trait/internal/UniformsManager.hx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Sources/armory/trait/internal/UniformsManager.hx b/Sources/armory/trait/internal/UniformsManager.hx index b33624d9..e559fc89 100644 --- a/Sources/armory/trait/internal/UniformsManager.hx +++ b/Sources/armory/trait/internal/UniformsManager.hx @@ -131,10 +131,17 @@ class UniformsManager extends Trait{ uniformExist = true; var object = Scene.active.root; // Map default texture to scene root - iron.data.Data.getImage(texture.default_image_file, function(image: kha.Image) { - setTextureValue(material, object, texture.link, image); - - }); + if(texture.default_image_file == null){ + setTextureValue(material, object, texture.link, null); + + } + else{ + iron.data.Data.getImage(texture.default_image_file, function(image: kha.Image) { + setTextureValue(material, object, texture.link, image); + + }); + + } register(Texture); } From 9ec425f6aec1dfa5afecef2399a84bffad993e65 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Mon, 26 Jul 2021 13:50:52 +0200 Subject: [PATCH 249/264] get file name only if specifiedin node --- blender/arm/material/cycles_nodes/nodes_texture.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/blender/arm/material/cycles_nodes/nodes_texture.py b/blender/arm/material/cycles_nodes/nodes_texture.py index 9952a0cb..379d4ca1 100644 --- a/blender/arm/material/cycles_nodes/nodes_texture.py +++ b/blender/arm/material/cycles_nodes/nodes_texture.py @@ -120,11 +120,11 @@ def parse_tex_image(node: bpy.types.ShaderNodeTexImage, out_socket: bpy.types.No if node.arm_material_param: tex_link = node.name is_arm_mat_param = True - if tex['file'] is not None: - tex_default_file = tex['file'] if tex is not None: state.curshader.write_textures += 1 + if node.arm_material_param and tex['file'] is not None: + tex_default_file = tex['file'] if use_color_out: to_linear = node.image is not None and node.image.colorspace_settings.name == 'sRGB' res = f'{c.texture_store(node, tex, tex_name, to_linear, tex_link=tex_link, default_value=tex_default_file, is_arm_mat_param=is_arm_mat_param)}.rgb' @@ -140,8 +140,8 @@ def parse_tex_image(node: bpy.types.ShaderNodeTexImage, out_socket: bpy.types.No 'file': '' } if use_color_out: - return '{0}.rgb'.format(c.texture_store(node, tex, tex_name, to_linear=False, tex_link=tex_link)) - return '{0}.a'.format(c.texture_store(node, tex, tex_name, to_linear=True, tex_link=tex_link)) + return '{0}.rgb'.format(c.texture_store(node, tex, tex_name, to_linear=False, tex_link=tex_link, is_arm_mat_param=is_arm_mat_param)) + return '{0}.a'.format(c.texture_store(node, tex, tex_name, to_linear=True, tex_link=tex_link, is_arm_mat_param=is_arm_mat_param)) # Pink color for missing texture else: From 9f7af6a1cda8f7ce3e47cdd4992f3454d5732877 Mon Sep 17 00:00:00 2001 From: QuantumCoderQC Date: Mon, 26 Jul 2021 18:02:23 +0200 Subject: [PATCH 250/264] Change terrain material bump output to normal --- blender/arm/props_ui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index e368dcc2..4a37b470 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -1923,7 +1923,7 @@ class ArmGenTerrainButton(bpy.types.Operator): 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[17], nodes['Bump'].outputs[0]) + links.new(nodes['Principled BSDF'].inputs[20], nodes['Bump'].outputs[0]) # Create sectors root_obj = bpy.data.objects.new("Terrain", None) From 13cf22ecc296f2e7569914acb447bd3810545433 Mon Sep 17 00:00:00 2001 From: tong Date: Sat, 24 Jul 2021 11:32:36 +0200 Subject: [PATCH 251/264] ArcBall axis trait prop --- Sources/armory/trait/ArcBall.hx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Sources/armory/trait/ArcBall.hx b/Sources/armory/trait/ArcBall.hx index ddf800f7..7f4edad5 100644 --- a/Sources/armory/trait/ArcBall.hx +++ b/Sources/armory/trait/ArcBall.hx @@ -6,6 +6,9 @@ import iron.math.Vec4; class ArcBall extends Trait { + @prop + public var axis = new Vec4(0, 0, 1); + public function new() { super(); @@ -17,10 +20,8 @@ class ArcBall extends Trait { var mouse = Input.getMouse(); if (mouse.down()) { - object.transform.rotate(new Vec4(0, 0, 1), -mouse.movementX / 100); - object.transform.buildMatrix(); + object.transform.rotate(axis, -mouse.movementX / 100); object.transform.rotate(object.transform.world.right(), -mouse.movementY / 100); - object.transform.buildMatrix(); } } } From 5f2dce140e2fe49796e269c0e0acbc7b44ec1a7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Mon, 26 Jul 2021 23:40:47 +0200 Subject: [PATCH 252/264] Windows: fix arm.utils.get_fp() if the project path is a drive root --- blender/arm/utils.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/blender/arm/utils.py b/blender/arm/utils.py index dc468a8d..7a9f1084 100755 --- a/blender/arm/utils.py +++ b/blender/arm/utils.py @@ -100,7 +100,17 @@ def get_fp(): else: s = bpy.data.filepath.split(os.path.sep) s.pop() - return os.path.sep.join(s) + s = os.path.sep.join(s) + if get_os_is_windows() and len(s) == 2 and s[1] == ':': + # If the project is located at a drive root (C:/ for example), + # then s = "C:". If joined later with another path, no path + # separator is added by default because C:some_path is valid + # Windows path syntax (some_path is then relative to the CWD on the + # C drive). We prevent this by manually adding the path separator + # in these cases. Please refer to the Python doc of os.path.join() + # for more details. + s += os.path.sep + return s def get_fp_build(): return os.path.join(get_fp(), build_dir()) From 4fc1f38b3bd5d8fe301eaaabbc0e9c2210c3060e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Tue, 27 Jul 2021 00:21:27 +0200 Subject: [PATCH 253/264] Continue thread polling after an exception in the done callback --- blender/arm/handlers.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/blender/arm/handlers.py b/blender/arm/handlers.py index f3d03f42..177a907c 100644 --- a/blender/arm/handlers.py +++ b/blender/arm/handlers.py @@ -116,7 +116,16 @@ def poll_threads() -> float: return 0.25 thread.join() - callback() + + try: + callback() + except Exception as e: + # If there is an exception, we can no longer return the time to + # the next call to this polling function, so to keep it running + # we re-register it and then raise the original exception. + bpy.app.timers.unregister(poll_threads) + bpy.app.timers.register(poll_threads, first_interval=0.01, persistent=True) + raise e # Quickly check if another thread has finished return 0.01 From 4c34c2a5ce783641eaf1ce5ba262c77ebed96d41 Mon Sep 17 00:00:00 2001 From: tong Date: Wed, 4 Aug 2021 22:09:54 +0200 Subject: [PATCH 254/264] PhysicsDrag trait limit props --- Sources/armory/trait/PhysicsDrag.hx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Sources/armory/trait/PhysicsDrag.hx b/Sources/armory/trait/PhysicsDrag.hx index bef674b5..111a4403 100755 --- a/Sources/armory/trait/PhysicsDrag.hx +++ b/Sources/armory/trait/PhysicsDrag.hx @@ -2,6 +2,7 @@ package armory.trait; import iron.Trait; import iron.system.Input; +import iron.math.Vec3; import iron.math.Vec4; import iron.math.Mat4; import iron.math.RayCaster; @@ -14,6 +15,11 @@ class PhysicsDrag extends Trait { public function new() { super(); } #else + @prop public var linearLowerLimit = new Vec3(0,0,0); + @prop public var linearUpperLimit = new Vec3(0,0,0); + @prop public var angularLowerLimit = new Vec3(-10,-10,-10); + @prop public var angularUpperLimit = new Vec3(10,10,10); + var pickConstraint: bullet.Bt.Generic6DofConstraint = null; var pickDist: Float; var pickedBody: RigidBody = null; @@ -56,10 +62,10 @@ class PhysicsDrag extends Trait { tr.setOrigin(localPivot); pickConstraint = new bullet.Bt.Generic6DofConstraint(b.body, tr, false); - pickConstraint.setLinearLowerLimit(new bullet.Bt.Vector3(0, 0, 0)); - pickConstraint.setLinearUpperLimit(new bullet.Bt.Vector3(0, 0, 0)); - pickConstraint.setAngularLowerLimit(new bullet.Bt.Vector3(-10, -10, -10)); - pickConstraint.setAngularUpperLimit(new bullet.Bt.Vector3(10, 10, 10)); + pickConstraint.setLinearLowerLimit(new bullet.Bt.Vector3(linearLowerLimit.x, linearLowerLimit.y, linearLowerLimit.z)); + pickConstraint.setLinearUpperLimit(new bullet.Bt.Vector3(linearUpperLimit.x, linearUpperLimit.y, linearUpperLimit.z)); + pickConstraint.setAngularLowerLimit(new bullet.Bt.Vector3(angularLowerLimit.x, angularLowerLimit.y, angularLowerLimit.z)); + pickConstraint.setAngularUpperLimit(new bullet.Bt.Vector3(angularUpperLimit.x, angularUpperLimit.y, angularUpperLimit.z)); physics.world.addConstraint(pickConstraint, false); /*pickConstraint.setParam(4, 0.8, 0); From ea8c13686c68a07672bf71ffa04cb7aa33b98f9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Wed, 4 Aug 2021 22:49:38 +0200 Subject: [PATCH 255/264] Implement basic addon reloading --- blender/arm/__init__.py | 26 +++++++++++++ blender/arm/api.py | 9 ++++- blender/arm/assets.py | 6 +++ blender/arm/exporter.py | 14 ++++++- blender/arm/exporter_opt.py | 15 ++++++-- blender/arm/handlers.py | 12 ++++++ blender/arm/keymap.py | 9 +++++ blender/arm/lib/armpack.py | 3 +- blender/arm/lib/server.py | 2 +- blender/arm/live_patch.py | 29 ++++++++++---- blender/arm/logicnode/__init__.py | 29 ++++++++++++-- blender/arm/logicnode/arm_nodes.py | 11 +++++- blender/arm/logicnode/arm_props.py | 14 +++++++ blender/arm/logicnode/arm_sockets.py | 5 +++ blender/arm/logicnode/replacement.py | 8 ++++ blender/arm/make.py | 17 +++++++++ blender/arm/make_logic.py | 9 +++++ blender/arm/make_renderpath.py | 14 ++++++- blender/arm/make_state.py | 33 ++++++++-------- blender/arm/make_world.py | 15 ++++++++ blender/arm/material/__init__.py | 1 + .../arm_nodes/custom_particle_node.py | 13 ++++++- .../material/arm_nodes/shader_data_node.py | 9 +++++ blender/arm/material/cycles.py | 17 +++++++++ .../arm/material/cycles_nodes/nodes_color.py | 12 ++++++ .../material/cycles_nodes/nodes_converter.py | 15 +++++++- .../arm/material/cycles_nodes/nodes_input.py | 12 ++++++ .../arm/material/cycles_nodes/nodes_shader.py | 8 ++++ .../material/cycles_nodes/nodes_texture.py | 16 +++++++- .../arm/material/cycles_nodes/nodes_vector.py | 17 +++++++-- blender/arm/material/make.py | 9 +++++ blender/arm/material/make_attrib.py | 13 +++++++ blender/arm/material/make_decal.py | 11 +++++- blender/arm/material/make_depth.py | 16 ++++++++ blender/arm/material/make_finalize.py | 8 ++++ blender/arm/material/make_mesh.py | 15 ++++++++ blender/arm/material/make_overlay.py | 9 +++++ blender/arm/material/make_particle.py | 12 ++++-- blender/arm/material/make_shader.py | 20 ++++++++++ blender/arm/material/make_skin.py | 7 ++++ blender/arm/material/make_transluc.py | 14 ++++++- blender/arm/material/make_voxel.py | 15 +++++--- blender/arm/material/mat_batch.py | 13 +++++-- blender/arm/material/mat_utils.py | 11 +++++- blender/arm/material/parser_state.py | 7 ++++ blender/arm/material/shader.py | 5 +++ blender/arm/node_utils.py | 7 ++++ blender/arm/nodes_logic.py | 10 +++++ blender/arm/nodes_material.py | 8 ++++ blender/arm/profiler.py | 7 ++++ blender/arm/props.py | 10 +++++ blender/arm/props_bake.py | 17 +++++++-- blender/arm/props_exporter.py | 17 +++++++-- blender/arm/props_lod.py | 1 - blender/arm/props_renderpath.py | 6 +++ blender/arm/props_traits.py | 18 +++++++-- blender/arm/props_traits_props.py | 2 + blender/arm/props_ui.py | 26 ++++++++++--- blender/arm/ui_icons.py | 36 ++++++++++++++---- blender/arm/utils.py | 17 +++++++-- blender/arm/write_data.py | 8 ++++ blender/arm/write_probes.py | 8 ++++ blender/start.py | 38 +++++++++++++++++++ 63 files changed, 738 insertions(+), 83 deletions(-) diff --git a/blender/arm/__init__.py b/blender/arm/__init__.py index e69de29b..eb89c678 100755 --- a/blender/arm/__init__.py +++ b/blender/arm/__init__.py @@ -0,0 +1,26 @@ +import importlib +import types + +# This gets cleared if this package/the __init__ module is reloaded +_module_cache: dict[str, types.ModuleType] = {} + + +def reload_module(module: types.ModuleType) -> types.ModuleType: + """Wrapper around importlib.reload() to make sure no module is + reloaded twice. + + Make sure to call this function in the same order in which the + modules are imported to make sure that the reloading respects the + module dependencies. Otherwise modules could depend on other modules + that are not yet reloaded. + + If you import classes or functions from a module, make sure to + re-import them after the module is reloaded. + """ + mod = _module_cache.get(module.__name__, None) + + if mod is None: + mod = importlib.reload(module) + _module_cache[module.__name__] = mod + + return mod diff --git a/blender/arm/api.py b/blender/arm/api.py index ed53a290..d66c9b8c 100644 --- a/blender/arm/api.py +++ b/blender/arm/api.py @@ -4,7 +4,14 @@ from bpy.types import Material, UILayout from arm.material.shader import ShaderContext -drivers: Dict[str, Dict] = dict() +if "DO_RELOAD_MODULE" in locals(): + import arm + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import ShaderContext +else: + drivers: Dict[str, Dict] = dict() + + DO_RELOAD_MODULE = True def add_driver(driver_name: str, diff --git a/blender/arm/assets.py b/blender/arm/assets.py index b0c1bc97..1882818d 100755 --- a/blender/arm/assets.py +++ b/blender/arm/assets.py @@ -6,6 +6,12 @@ import bpy import arm.log as log import arm.utils +if "DO_RELOAD_MODULE" in locals(): + log = arm.reload_module(log) + arm.utils = arm.reload_module(arm.utils) +else: + DO_RELOAD_MODULE = True + assets = [] reserved_names = ['return.'] khafile_params = [] diff --git a/blender/arm/exporter.py b/blender/arm/exporter.py index 10d23e13..92d28a2a 100755 --- a/blender/arm/exporter.py +++ b/blender/arm/exporter.py @@ -32,7 +32,19 @@ import arm.material.mat_batch as mat_batch import arm.utils import arm.profiler -import arm.log as log +if "DO_RELOAD_MODULE" in locals(): + assets = arm.reload_module(assets) + exporter_opt = arm.reload_module(exporter_opt) + log = arm.reload_module(log) + make_renderpath = arm.reload_module(make_renderpath) + cycles = arm.reload_module(cycles) + make_material = arm.reload_module(make_material) + mat_batch = arm.reload_module(mat_batch) + arm.utils = arm.reload_module(arm.utils) + arm.profiler = arm.reload_module(arm.profiler) +else: + DO_RELOAD_MODULE = True + @unique class NodeType(Enum): diff --git a/blender/arm/exporter_opt.py b/blender/arm/exporter_opt.py index 60c64ae6..520a83a1 100644 --- a/blender/arm/exporter_opt.py +++ b/blender/arm/exporter_opt.py @@ -1,11 +1,20 @@ +""" +Exports smaller geometry but is slower. +To be replaced with https://github.com/zeux/meshoptimizer +""" + from mathutils import * import numpy as np -import arm.utils import arm.log as log +import arm.utils + +if "DO_RELOAD_MODULE" in locals(): + log = arm.reload_module(log) + arm.utils = arm.reload_module(arm.utils) +else: + DO_RELOAD_MODULE = True -# Exports smaller geometry but is slower -# To be replaced with https://github.com/zeux/meshoptimizer class Vertex: __slots__ = ("co", "normal", "uvs", "col", "loop_indices", "index", "bone_weights", "bone_indices", "bone_count", "vertex_index") diff --git a/blender/arm/handlers.py b/blender/arm/handlers.py index 177a907c..f77e117a 100644 --- a/blender/arm/handlers.py +++ b/blender/arm/handlers.py @@ -15,6 +15,18 @@ import arm.make_state as state import arm.props as props import arm.utils +if "DO_RELOAD_MODULE" in locals(): + arm.api = arm.reload_module(arm.api) + live_patch = arm.reload_module(live_patch) + arm_nodes = arm.reload_module(arm_nodes) + arm.nodes_logic = arm.reload_module(arm.nodes_logic) + make = arm.reload_module(make) + state = arm.reload_module(state) + props = arm.reload_module(props) + arm.utils = arm.reload_module(arm.utils) +else: + DO_RELOAD_MODULE = True + @persistent def on_depsgraph_update_post(self): diff --git a/blender/arm/keymap.py b/blender/arm/keymap.py index 77e09183..40dabd09 100644 --- a/blender/arm/keymap.py +++ b/blender/arm/keymap.py @@ -1,8 +1,16 @@ import bpy + import arm.props_ui as props_ui +if "DO_RELOAD_MODULE" in locals(): + import arm + props_ui = arm.reload_module(props_ui) +else: + DO_RELOAD_MODULE = True + arm_keymaps = [] + def register(): wm = bpy.context.window_manager addon_keyconfig = wm.keyconfigs.addon @@ -21,6 +29,7 @@ def register(): km.keymap_items.new("tlm.clean_lightmaps", type='F7', value='PRESS') arm_keymaps.append(km) + def unregister(): wm = bpy.context.window_manager for km in arm_keymaps: diff --git a/blender/arm/lib/armpack.py b/blender/arm/lib/armpack.py index f2f5d23d..7962608f 100755 --- a/blender/arm/lib/armpack.py +++ b/blender/arm/lib/armpack.py @@ -20,9 +20,10 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # -import struct import io import numpy as np +import struct + def _pack_integer(obj, fp): if obj < 0: diff --git a/blender/arm/lib/server.py b/blender/arm/lib/server.py index 9774945c..21492324 100755 --- a/blender/arm/lib/server.py +++ b/blender/arm/lib/server.py @@ -1,7 +1,7 @@ +import atexit import http.server import socketserver import subprocess -import atexit haxe_server = None diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index b2bf81a8..6a1c9fea 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -5,23 +5,37 @@ from typing import Any, Type import bpy import arm.assets -import arm.node_utils from arm.exporter import ArmoryExporter import arm.log as log from arm.logicnode.arm_nodes import ArmLogicTreeNode import arm.make as make import arm.make_state as state +import arm.node_utils import arm.utils -# Current patch id -patch_id = 0 +if "DO_RELOAD_MODULE" in locals(): + arm.assets = arm.reload_module(arm.assets) + arm.exporter = arm.reload_module(arm.exporter) + from arm.exporter import ArmoryExporter + log = arm.reload_module(log) + arm.logicnode.arm_nodes = arm.reload_module(arm.logicnode.arm_nodes) + from arm.logicnode.arm_nodes import ArmLogicTreeNode + make = arm.reload_module(make) + state = arm.reload_module(state) + arm.node_utils = arm.reload_module(arm.node_utils) + arm.utils = arm.reload_module(arm.utils) +else: + DO_RELOAD_MODULE = True + + patch_id = 0 + """Current patch id""" + + __running = False + """Whether live patch is currently active""" # Any object can act as a message bus owner msgbus_owner = object() -# Whether live patch is currently active -__running = False - def start(): """Start the live patch session.""" @@ -43,9 +57,10 @@ def start(): def stop(): """Stop the live patch session.""" - global __running + global __running, patch_id if __running: __running = False + patch_id = 0 log.debug("Live patch session stopped") bpy.msgbus.clear_by_owner(msgbus_owner) diff --git a/blender/arm/logicnode/__init__.py b/blender/arm/logicnode/__init__.py index 716ecd37..aaaceb5d 100644 --- a/blender/arm/logicnode/__init__.py +++ b/blender/arm/logicnode/__init__.py @@ -1,13 +1,29 @@ import importlib import inspect import pkgutil +import sys +import arm import arm.logicnode.arm_nodes as arm_nodes +from arm.logicnode.arm_props import * import arm.logicnode.arm_sockets as arm_sockets +from arm.logicnode.replacement import NodeReplacement + +if "DO_RELOAD_MODULE" in locals(): + arm_nodes = arm.reload_module(arm_nodes) + arm.logicnode.arm_props = arm.reload_module(arm.logicnode.arm_props) + from arm.logicnode.arm_props import * + arm_sockets = arm.reload_module(arm_sockets) + arm.logicnode.replacement = arm.reload_module(arm.logicnode.replacement) + from arm.logicnode.replacement import NodeReplacement + + HAS_RELOADED = True +else: + DO_RELOAD_MODULE = True def init_categories(): - # Register node menu categories + """Register default node menu categories.""" arm_nodes.add_category('Logic', icon='OUTLINER', section="basic", description="Logic nodes are used to control execution flow using branching, loops, gates etc.") arm_nodes.add_category('Event', icon='INFO', section="basic") @@ -56,8 +72,15 @@ def init_nodes(): # The package must be loaded as well so that the modules from that package can be accessed (see the # pkgutil.walk_packages documentation for more information on this) loader.find_module(module_name).load_module(module_name) - else: - _module = importlib.import_module(module_name) + + # Only look at modules in sub packages + elif module_name.rsplit('.', 1)[0] != __package__: + if 'HAS_RELOADED' not in globals(): + _module = importlib.import_module(module_name) + else: + # Reload the module if the SDK was reloaded at least once + _module = importlib.reload(sys.modules[module_name]) + for name, obj in inspect.getmembers(_module, inspect.isclass): if name == "ArmLogicTreeNode": continue diff --git a/blender/arm/logicnode/arm_nodes.py b/blender/arm/logicnode/arm_nodes.py index 85ae3730..8ab7381e 100644 --- a/blender/arm/logicnode/arm_nodes.py +++ b/blender/arm/logicnode/arm_nodes.py @@ -1,5 +1,5 @@ -import itertools from collections import OrderedDict +import itertools from typing import Any, Generator, List, Optional, Type from typing import OrderedDict as ODict # Prevent naming conflicts @@ -14,6 +14,15 @@ from arm.logicnode.arm_props import * from arm.logicnode.replacement import NodeReplacement import arm.node_utils +if "DO_RELOAD_MODULE" in locals(): + arm.logicnode.arm_props = arm.reload_module(arm.logicnode.arm_props) + from arm.logicnode.arm_props import * + arm.logicnode.replacement = arm.reload_module(arm.logicnode.replacement) + from arm.logicnode.replacement import NodeReplacement + arm.node_utils = arm.reload_module(arm.node_utils) +else: + DO_RELOAD_MODULE = True + # When passed as a category to add_node(), this will use the capitalized # name of the package of the node as the category to make renaming # categories easier. diff --git a/blender/arm/logicnode/arm_props.py b/blender/arm/logicnode/arm_props.py index 1a32cb28..c87e7534 100644 --- a/blender/arm/logicnode/arm_props.py +++ b/blender/arm/logicnode/arm_props.py @@ -15,6 +15,20 @@ from typing import Any, Callable, Sequence, Union import bpy from bpy.props import * +__all__ = [ + 'HaxeBoolProperty', + 'HaxeBoolVectorProperty', + 'HaxeCollectionProperty', + 'HaxeEnumProperty', + 'HaxeFloatProperty', + 'HaxeFloatVectorProperty', + 'HaxeIntProperty', + 'HaxeIntVectorProperty', + 'HaxePointerProperty', + 'HaxeStringProperty', + 'RemoveHaxeProperty' +] + def __haxe_prop(prop_type: Callable, prop_name: str, *args, **kwargs) -> Any: """Declares a logic node property as a property that will be diff --git a/blender/arm/logicnode/arm_sockets.py b/blender/arm/logicnode/arm_sockets.py index 58f2ea70..df36658e 100644 --- a/blender/arm/logicnode/arm_sockets.py +++ b/blender/arm/logicnode/arm_sockets.py @@ -4,6 +4,11 @@ from bpy.types import NodeSocket import arm.utils +if "DO_RELOAD_MODULE" in locals(): + arm.utils = arm.reload_module(arm.utils) +else: + DO_RELOAD_MODULE = True + def _on_update_socket(self, context): self.node.on_socket_val_update(context, self) diff --git a/blender/arm/logicnode/replacement.py b/blender/arm/logicnode/replacement.py index b703d83b..d0efc6ec 100644 --- a/blender/arm/logicnode/replacement.py +++ b/blender/arm/logicnode/replacement.py @@ -20,6 +20,14 @@ import arm.logicnode.arm_nodes as arm_nodes import arm.logicnode.arm_sockets import arm.node_utils as node_utils +if "DO_RELOAD_MODULE" in locals(): + log = arm.reload_module(log) + arm_nodes = arm.reload_module(arm_nodes) + arm.logicnode.arm_sockets = arm.reload_module(arm.logicnode.arm_sockets) + node_utils = arm.reload_module(node_utils) +else: + DO_RELOAD_MODULE = True + # List of errors that occurred during the replacement # Format: (error identifier, node.bl_idname (or None), tree name, exception traceback (optional)) replacement_errors: List[Tuple[str, Optional[str], str, Optional[str]]] = [] diff --git a/blender/arm/make.py b/blender/arm/make.py index a4da2dbf..cc599c29 100755 --- a/blender/arm/make.py +++ b/blender/arm/make.py @@ -27,6 +27,23 @@ import arm.make_world as make_world import arm.utils import arm.write_data as write_data +if "DO_RELOAD_MODULE" in locals(): + assets = arm.reload_module(assets) + arm.exporter = arm.reload_module(arm.exporter) + from arm.exporter import ArmoryExporter + arm.lib.make_datas = arm.reload_module(arm.lib.make_datas) + arm.lib.server = arm.reload_module(arm.lib.server) + live_patch = arm.reload_module(live_patch) + log = arm.reload_module(log) + make_logic = arm.reload_module(make_logic) + make_renderpath = arm.reload_module(make_renderpath) + state = arm.reload_module(state) + make_world = arm.reload_module(make_world) + arm.utils = arm.reload_module(arm.utils) + write_data = arm.reload_module(write_data) +else: + DO_RELOAD_MODULE = True + scripts_mtime = 0 # Monitor source changes profile_time = 0 diff --git a/blender/arm/make_logic.py b/blender/arm/make_logic.py index 5ecf42a1..a4195bb8 100755 --- a/blender/arm/make_logic.py +++ b/blender/arm/make_logic.py @@ -8,6 +8,15 @@ import arm.log import arm.node_utils import arm.utils +if "DO_RELOAD_MODULE" in locals(): + arm.exporter = arm.reload_module(arm.exporter) + from arm.exporter import ArmoryExporter + arm.log = arm.reload_module(arm.log) + arm.node_utils = arm.reload_module(arm.node_utils) + arm.utils = arm.reload_module(arm.utils) +else: + DO_RELOAD_MODULE = True + parsed_nodes = [] parsed_ids = dict() # Sharing node data function_nodes = dict() diff --git a/blender/arm/make_renderpath.py b/blender/arm/make_renderpath.py index ef296d21..7b2e0dde 100755 --- a/blender/arm/make_renderpath.py +++ b/blender/arm/make_renderpath.py @@ -1,9 +1,19 @@ import bpy + +import arm.api import arm.assets as assets -import arm.utils import arm.log as log import arm.make_state as state -import arm.api +import arm.utils + +if "DO_RELOAD_MODULE" in locals(): + arm.api = arm.reload_module(arm.api) + assets = arm.reload_module(assets) + log = arm.reload_module(log) + state = arm.reload_module(state) + arm.utils = arm.reload_module(arm.utils) +else: + DO_RELOAD_MODULE = True callback = None diff --git a/blender/arm/make_state.py b/blender/arm/make_state.py index b83b8726..bf6c9f97 100644 --- a/blender/arm/make_state.py +++ b/blender/arm/make_state.py @@ -1,15 +1,18 @@ -redraw_ui = False -target = 'krom' -last_target = 'krom' -export_gapi = '' -last_resx = 0 -last_resy = 0 -last_scene = '' -last_world_defs = '' -proc_play = None -proc_build = None -proc_publish_build = None -mod_scripts = [] -is_export = False -is_play = False -is_publish = False +if "DO_RELOAD_MODULE" not in locals(): + DO_RELOAD_MODULE = True + + redraw_ui = False + target = 'krom' + last_target = 'krom' + export_gapi = '' + last_resx = 0 + last_resy = 0 + last_scene = '' + last_world_defs = '' + proc_play = None + proc_build = None + proc_publish_build = None + mod_scripts = [] + is_export = False + is_play = False + is_publish = False diff --git a/blender/arm/make_world.py b/blender/arm/make_world.py index 71dac33b..3c176d06 100755 --- a/blender/arm/make_world.py +++ b/blender/arm/make_world.py @@ -12,6 +12,21 @@ import arm.node_utils as node_utils import arm.utils import arm.write_probes as write_probes +if "DO_RELOAD_MODULE" in locals(): + arm.assets = arm.reload_module(arm.assets) + arm.log = arm.reload_module(arm.log) + arm.material = arm.reload_module(arm.material) + arm.material.parser_state = arm.reload_module(arm.material.parser_state) + from arm.material.parser_state import ParserState, ParserContext + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import ShaderContext, Shader + cycles = arm.reload_module(cycles) + node_utils = arm.reload_module(node_utils) + arm.utils = arm.reload_module(arm.utils) + write_probes = arm.reload_module(write_probes) +else: + DO_RELOAD_MODULE = True + callback = None shader_datas = [] diff --git a/blender/arm/material/__init__.py b/blender/arm/material/__init__.py index e69de29b..661b7767 100755 --- a/blender/arm/material/__init__.py +++ b/blender/arm/material/__init__.py @@ -0,0 +1 @@ +import arm diff --git a/blender/arm/material/arm_nodes/custom_particle_node.py b/blender/arm/material/arm_nodes/custom_particle_node.py index 8c9d9f63..fbb28e78 100644 --- a/blender/arm/material/arm_nodes/custom_particle_node.py +++ b/blender/arm/material/arm_nodes/custom_particle_node.py @@ -5,6 +5,17 @@ from arm.material.arm_nodes.arm_nodes import add_node from arm.material.shader import Shader from arm.material.cycles import * +if "DO_RELOAD_MODULE" in locals(): + import arm + arm.material.arm_nodes.arm_nodes = arm.reload_module(arm.material.arm_nodes.arm_nodes) + from arm.material.arm_nodes.arm_nodes import add_node + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import Shader + arm.material.cycles = arm.reload_module(arm.material.cycles) + from arm.material.cycles import * +else: + DO_RELOAD_MODULE = True + class CustomParticleNode(Node): """Input data for paricles.""" @@ -174,7 +185,7 @@ class CustomParticleNode(Node): if self.posZ: vertshdr.write(f'spos.z += {pos}.z;') - + vertshdr.write('wposition = vec4(W * spos).xyz;') diff --git a/blender/arm/material/arm_nodes/shader_data_node.py b/blender/arm/material/arm_nodes/shader_data_node.py index 8ab0aaa6..ec7d2077 100644 --- a/blender/arm/material/arm_nodes/shader_data_node.py +++ b/blender/arm/material/arm_nodes/shader_data_node.py @@ -4,6 +4,15 @@ from bpy.types import Node from arm.material.arm_nodes.arm_nodes import add_node from arm.material.shader import Shader +if "DO_RELOAD_MODULE" in locals(): + import arm + arm.material.arm_nodes.arm_nodes = arm.reload_module(arm.material.arm_nodes.arm_nodes) + from arm.material.arm_nodes.arm_nodes import add_node + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import Shader +else: + DO_RELOAD_MODULE = True + class ShaderDataNode(Node): """Allows access to shader data such as uniforms and inputs.""" diff --git a/blender/arm/material/cycles.py b/blender/arm/material/cycles.py index 625c24c2..5e20f4dd 100644 --- a/blender/arm/material/cycles.py +++ b/blender/arm/material/cycles.py @@ -30,6 +30,23 @@ from arm.material.parser_state import ParserState, ParserContext from arm.material.shader import Shader, ShaderContext, floatstr, vec3str import arm.utils +if "DO_RELOAD_MODULE" in locals(): + arm.assets = arm.reload_module(arm.assets) + log = arm.reload_module(log) + arm.make_state = arm.reload_module(arm.make_state) + c_functions = arm.reload_module(c_functions) + arm.material.cycles_nodes = arm.reload_module(arm.material.cycles_nodes) + from arm.material.cycles_nodes import * + mat_state = arm.reload_module(mat_state) + arm.material.parser_state = arm.reload_module(arm.material.parser_state) + from arm.material.parser_state import ParserState, ParserContext + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import Shader, ShaderContext, floatstr, vec3str + arm.utils = arm.reload_module(arm.utils) +else: + DO_RELOAD_MODULE = True + + # Particle info export particle_info: Dict[str, bool] = {} diff --git a/blender/arm/material/cycles_nodes/nodes_color.py b/blender/arm/material/cycles_nodes/nodes_color.py index 1b7dff08..08a181f3 100644 --- a/blender/arm/material/cycles_nodes/nodes_color.py +++ b/blender/arm/material/cycles_nodes/nodes_color.py @@ -6,6 +6,18 @@ import arm.material.cycles_functions as c_functions from arm.material.parser_state import ParserState from arm.material.shader import floatstr, vec3str +if "DO_RELOAD_MODULE" in locals(): + import arm + log = arm.reload_module(log) + c = arm.reload_module(c) + c_functions = arm.reload_module(c_functions) + arm.material.parser_state = arm.reload_module(arm.material.parser_state) + from arm.material.parser_state import ParserState + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import floatstr, vec3str +else: + DO_RELOAD_MODULE = True + def parse_brightcontrast(node: bpy.types.ShaderNodeBrightContrast, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: out_col = c.parse_vector_input(node.inputs[0]) diff --git a/blender/arm/material/cycles_nodes/nodes_converter.py b/blender/arm/material/cycles_nodes/nodes_converter.py index 813a1d87..8a241b2d 100644 --- a/blender/arm/material/cycles_nodes/nodes_converter.py +++ b/blender/arm/material/cycles_nodes/nodes_converter.py @@ -8,6 +8,19 @@ import arm.material.cycles_functions as c_functions from arm.material.parser_state import ParserState from arm.material.shader import floatstr, vec3str +if "DO_RELOAD_MODULE" in locals(): + import arm + log = arm.reload_module(log) + c = arm.reload_module(c) + c_functions = arm.reload_module(c_functions) + arm.material.parser_state = arm.reload_module(arm.material.parser_state) + from arm.material.parser_state import ParserState + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import floatstr, vec3str +else: + DO_RELOAD_MODULE = True + + def parse_maprange(node: bpy.types.ShaderNodeMapRange, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: interp = node.interpolation_type @@ -40,7 +53,7 @@ def parse_maprange(node: bpy.types.ShaderNodeMapRange, out_socket: bpy.types.Nod return f'map_range_smootherstep({value}, {fromMin}, {fromMax}, {toMin}, {toMax})' def parse_blackbody(node: bpy.types.ShaderNodeBlackbody, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: - + t = c.parse_value_input(node.inputs[0]) state.curshader.add_function(c_functions.str_blackbody) diff --git a/blender/arm/material/cycles_nodes/nodes_input.py b/blender/arm/material/cycles_nodes/nodes_input.py index 335e56f6..1eb5c5db 100644 --- a/blender/arm/material/cycles_nodes/nodes_input.py +++ b/blender/arm/material/cycles_nodes/nodes_input.py @@ -10,6 +10,18 @@ from arm.material.parser_state import ParserState, ParserContext from arm.material.shader import floatstr, vec3str import arm.utils +if "DO_RELOAD_MODULE" in locals(): + log = arm.reload_module(log) + c = arm.reload_module(c) + c_functions = arm.reload_module(c_functions) + arm.material.parser_state = arm.reload_module(arm.material.parser_state) + from arm.material.parser_state import ParserState, ParserContext + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import floatstr, vec3str + arm.utils = arm.reload_module(arm.utils) +else: + DO_RELOAD_MODULE = True + def parse_attribute(node: bpy.types.ShaderNodeAttribute, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]: out_type = 'float' if out_socket.type == 'VALUE' else 'vec3' diff --git a/blender/arm/material/cycles_nodes/nodes_shader.py b/blender/arm/material/cycles_nodes/nodes_shader.py index 4e593bd0..45fec6be 100644 --- a/blender/arm/material/cycles_nodes/nodes_shader.py +++ b/blender/arm/material/cycles_nodes/nodes_shader.py @@ -4,6 +4,14 @@ from bpy.types import NodeSocket import arm.material.cycles as c from arm.material.parser_state import ParserState +if "DO_RELOAD_MODULE" in locals(): + import arm + c = arm.reload_module(c) + arm.material.parser_state = arm.reload_module(arm.material.parser_state) + from arm.material.parser_state import ParserState +else: + DO_RELOAD_MODULE = True + def parse_mixshader(node: bpy.types.ShaderNodeMixShader, out_socket: NodeSocket, state: ParserState) -> None: prefix = '' if node.inputs[0].is_linked else 'const ' diff --git a/blender/arm/material/cycles_nodes/nodes_texture.py b/blender/arm/material/cycles_nodes/nodes_texture.py index 379d4ca1..406dfba1 100644 --- a/blender/arm/material/cycles_nodes/nodes_texture.py +++ b/blender/arm/material/cycles_nodes/nodes_texture.py @@ -6,13 +6,27 @@ import bpy import arm.assets as assets import arm.log as log -import arm.material.cycles_functions as c_functions import arm.material.cycles as c +import arm.material.cycles_functions as c_functions from arm.material.parser_state import ParserState, ParserContext from arm.material.shader import floatstr, vec3str import arm.utils import arm.write_probes as write_probes +if "DO_RELOAD_MODULE" in locals(): + assets = arm.reload_module(assets) + log = arm.reload_module(log) + c = arm.reload_module(c) + c_functions = arm.reload_module(c_functions) + arm.material.parser_state = arm.reload_module(arm.material.parser_state) + from arm.material.parser_state import ParserState, ParserContext + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import floatstr, vec3str + arm.utils = arm.reload_module(arm.utils) + write_probes = arm.reload_module(write_probes) +else: + DO_RELOAD_MODULE = True + def parse_tex_brick(node: bpy.types.ShaderNodeTexBrick, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]: state.curshader.add_function(c_functions.str_tex_brick) diff --git a/blender/arm/material/cycles_nodes/nodes_vector.py b/blender/arm/material/cycles_nodes/nodes_vector.py index 5f78a70b..3a40c378 100644 --- a/blender/arm/material/cycles_nodes/nodes_vector.py +++ b/blender/arm/material/cycles_nodes/nodes_vector.py @@ -8,6 +8,17 @@ import arm.material.cycles_functions as c_functions from arm.material.parser_state import ParserState from arm.material.shader import floatstr, vec3str +if "DO_RELOAD_MODULE" in locals(): + import arm + c = arm.reload_module(c) + c_functions = arm.reload_module(c_functions) + arm.material.parser_state = arm.reload_module(arm.material.parser_state) + from arm.material.parser_state import ParserState + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import floatstr, vec3str +else: + DO_RELOAD_MODULE = True + def parse_curvevec(node: bpy.types.ShaderNodeVectorCurve, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: fac = c.parse_value_input(node.inputs[0]) @@ -144,7 +155,7 @@ def parse_displacement(node: bpy.types.ShaderNodeDisplacement, out_socket: bpy.t return f'(vec3({height}) * {scale})' def parse_vectorrotate(node: bpy.types.ShaderNodeVectorRotate, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: - + type = node.rotation_type input_vector: bpy.types.NodeSocket = c.parse_vector_input(node.inputs[0]) input_center: bpy.types.NodeSocket = c.parse_vector_input(node.inputs[1]) @@ -166,9 +177,9 @@ def parse_vectorrotate(node: bpy.types.ShaderNodeVectorRotate, out_socket: bpy.t elif type == 'Y_AXIS': return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(0.0, 1.0, 0.0), {input_angle} * {input_invert}) + {input_center} )' elif type == 'Z_AXIS': - return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(0.0, 0.0, 1.0), {input_angle} * {input_invert}) + {input_center} )' + return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(0.0, 0.0, 1.0), {input_angle} * {input_invert}) + {input_center} )' elif type == 'EULER_XYZ': state.curshader.add_function(c_functions.str_euler_to_mat3) return f'vec3( mat3(({input_invert} < 0.0) ? transpose(euler_to_mat3({input_rotation})) : euler_to_mat3({input_rotation})) * ({input_vector} - {input_center}) + {input_center})' - + return f'(vec3(1.0, 0.0, 0.0))' diff --git a/blender/arm/material/make.py b/blender/arm/material/make.py index 13a79d33..b067c180 100755 --- a/blender/arm/material/make.py +++ b/blender/arm/material/make.py @@ -10,6 +10,15 @@ import arm.material.mat_batch as mat_batch import arm.node_utils import arm.utils +if "DO_RELOAD_MODULE" in locals(): + cycles = arm.reload_module(cycles) + make_shader = arm.reload_module(make_shader) + mat_batch = arm.reload_module(mat_batch) + arm.node_utils = arm.reload_module(arm.node_utils) + arm.utils = arm.reload_module(arm.utils) +else: + DO_RELOAD_MODULE = True + def glsl_value(val): if str(type(val)) == "": diff --git a/blender/arm/material/make_attrib.py b/blender/arm/material/make_attrib.py index bc23a620..f82a3ab4 100644 --- a/blender/arm/material/make_attrib.py +++ b/blender/arm/material/make_attrib.py @@ -9,6 +9,19 @@ import arm.material.make_tess as make_tess from arm.material.shader import Shader, ShaderContext import arm.utils +if "DO_RELOAD_MODULE" in locals(): + cycles = arm.reload_module(cycles) + mat_state = arm.reload_module(mat_state) + make_skin = arm.reload_module(make_skin) + make_particle = arm.reload_module(make_particle) + make_inst = arm.reload_module(make_inst) + make_tess = arm.reload_module(make_tess) + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import Shader, ShaderContext + arm.utils = arm.reload_module(arm.utils) +else: + DO_RELOAD_MODULE = True + def write_vertpos(vert): billboard = mat_state.material.arm_billboard diff --git a/blender/arm/material/make_decal.py b/blender/arm/material/make_decal.py index 32e896e9..c17b1ead 100644 --- a/blender/arm/material/make_decal.py +++ b/blender/arm/material/make_decal.py @@ -1,10 +1,19 @@ import bpy + import arm.material.cycles as cycles import arm.material.mat_state as mat_state -import arm.material.mat_utils as mat_utils import arm.material.make_finalize as make_finalize import arm.utils +if "DO_RELOAD_MODULE" in locals(): + cycles = arm.reload_module(cycles) + mat_state = arm.reload_module(mat_state) + make_finalize = arm.reload_module(make_finalize) + arm.utils = arm.reload_module(arm.utils) +else: + DO_RELOAD_MODULE = True + + def make(context_id): wrd = bpy.data.worlds['Arm'] diff --git a/blender/arm/material/make_depth.py b/blender/arm/material/make_depth.py index aa1f0864..4f9115ca 100644 --- a/blender/arm/material/make_depth.py +++ b/blender/arm/material/make_depth.py @@ -1,4 +1,5 @@ import bpy + import arm.material.cycles as cycles import arm.material.mat_state as mat_state import arm.material.mat_utils as mat_utils @@ -10,6 +11,21 @@ import arm.material.make_finalize as make_finalize import arm.assets as assets import arm.utils +if "DO_RELOAD_MODULE" in locals(): + cycles = arm.reload_module(cycles) + mat_state = arm.reload_module(mat_state) + mat_utils = arm.reload_module(mat_utils) + make_skin = arm.reload_module(make_skin) + make_inst = arm.reload_module(make_inst) + make_tess = arm.reload_module(make_tess) + make_particle = arm.reload_module(make_particle) + make_finalize = arm.reload_module(make_finalize) + assets = arm.reload_module(assets) + arm.utils = arm.reload_module(arm.utils) +else: + DO_RELOAD_MODULE = True + + def make(context_id, rpasses, shadowmap=False): is_disp = mat_utils.disp_linked(mat_state.output_node) diff --git a/blender/arm/material/make_finalize.py b/blender/arm/material/make_finalize.py index afeeee8f..6477e55c 100644 --- a/blender/arm/material/make_finalize.py +++ b/blender/arm/material/make_finalize.py @@ -3,6 +3,14 @@ import bpy import arm.material.make_tess as make_tess from arm.material.shader import ShaderContext +if "DO_RELOAD_MODULE" in locals(): + import arm + make_tess = arm.reload_module(make_tess) + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import ShaderContext +else: + DO_RELOAD_MODULE = True + def make(con_mesh: ShaderContext): vert = con_mesh.vert diff --git a/blender/arm/material/make_mesh.py b/blender/arm/material/make_mesh.py index db9a2c28..652e6d91 100644 --- a/blender/arm/material/make_mesh.py +++ b/blender/arm/material/make_mesh.py @@ -1,4 +1,5 @@ import bpy + import arm.assets as assets import arm.material.mat_state as mat_state import arm.material.mat_utils as mat_utils @@ -10,6 +11,20 @@ import arm.material.make_finalize as make_finalize import arm.material.make_attrib as make_attrib import arm.utils +if "DO_RELOAD_MODULE" in locals(): + assets = arm.reload_module(assets) + mat_state = arm.reload_module(mat_state) + mat_utils = arm.reload_module(mat_utils) + cycles = arm.reload_module(cycles) + make_tess = arm.reload_module(make_tess) + make_particle = arm.reload_module(make_particle) + make_cluster = arm.reload_module(make_cluster) + make_finalize = arm.reload_module(make_finalize) + make_attrib = arm.reload_module(make_attrib) + arm.utils = arm.reload_module(arm.utils) +else: + DO_RELOAD_MODULE = True + is_displacement = False write_material_attribs = None write_material_attribs_post = None diff --git a/blender/arm/material/make_overlay.py b/blender/arm/material/make_overlay.py index 47c71df3..de0b0b35 100644 --- a/blender/arm/material/make_overlay.py +++ b/blender/arm/material/make_overlay.py @@ -3,6 +3,15 @@ import arm.material.make_mesh as make_mesh import arm.material.mat_state as mat_state import arm.material.mat_utils as mat_utils +if "DO_RELOAD_MODULE" in locals(): + import arm + make_finalize = arm.reload_module(make_finalize) + make_mesh = arm.reload_module(make_mesh) + mat_state = arm.reload_module(mat_state) + mat_utils = arm.reload_module(mat_utils) +else: + DO_RELOAD_MODULE = True + def make(context_id): con = { 'name': context_id, 'depth_write': True, 'compare_mode': 'less', 'cull_mode': 'clockwise' } diff --git a/blender/arm/material/make_particle.py b/blender/arm/material/make_particle.py index 2c9a71e2..c62d2040 100644 --- a/blender/arm/material/make_particle.py +++ b/blender/arm/material/make_particle.py @@ -1,7 +1,13 @@ - import arm.utils import arm.material.mat_state as mat_state +if "DO_RELOAD_MODULE" in locals(): + arm.utils = arm.reload_module(arm.utils) + mat_state = arm.reload_module(mat_state) +else: + DO_RELOAD_MODULE = True + + def write(vert, particle_info=None, shadowmap=False): # Outs @@ -17,7 +23,7 @@ def write(vert, particle_info=None, shadowmap=False): str_tex_hash = "float fhash(float n) { return fract(sin(n) * 43758.5453); }\n" vert.add_function(str_tex_hash) - + prep = 'float ' if out_age: prep = '' @@ -45,7 +51,7 @@ def write(vert, particle_info=None, shadowmap=False): vert.write('}') # vert.write('p_age /= 2;') # Match - + # object_align_factor / 2 + gxyz prep = 'vec3 ' if out_velocity: diff --git a/blender/arm/material/make_shader.py b/blender/arm/material/make_shader.py index 93b40391..87307799 100644 --- a/blender/arm/material/make_shader.py +++ b/blender/arm/material/make_shader.py @@ -22,6 +22,26 @@ import arm.material.mat_utils as mat_utils from arm.material.shader import Shader, ShaderContext, ShaderData import arm.utils +if "DO_RELOAD_MODULE" in locals(): + arm.api = arm.reload_module(arm.api) + assets = arm.reload_module(assets) + arm.exporter = arm.reload_module(arm.exporter) + log = arm.reload_module(log) + cycles = arm.reload_module(cycles) + make_decal = arm.reload_module(make_decal) + make_depth = arm.reload_module(make_depth) + make_mesh = arm.reload_module(make_mesh) + make_overlay = arm.reload_module(make_overlay) + make_transluc = arm.reload_module(make_transluc) + make_voxel = arm.reload_module(make_voxel) + mat_state = arm.reload_module(mat_state) + mat_utils = arm.reload_module(mat_utils) + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import Shader, ShaderContext, ShaderData + arm.utils = arm.reload_module(arm.utils) +else: + DO_RELOAD_MODULE = True + rpass_hook = None diff --git a/blender/arm/material/make_skin.py b/blender/arm/material/make_skin.py index d6ea4177..8730b089 100644 --- a/blender/arm/material/make_skin.py +++ b/blender/arm/material/make_skin.py @@ -1,5 +1,11 @@ import arm.utils +if "DO_RELOAD_MODULE" in locals(): + arm.utils = arm.reload_module(arm.utils) +else: + DO_RELOAD_MODULE = True + + def skin_pos(vert): vert.add_include('compiled.inc') @@ -15,6 +21,7 @@ def skin_pos(vert): vert.write_attrib('spos.xyz += 2.0 * (skinA.w * skinB.xyz - skinB.w * skinA.xyz + cross(skinA.xyz, skinB.xyz)); // Translate') vert.write_attrib('spos.xyz /= posUnpack;') + def skin_nor(vert, prep): rpdat = arm.utils.get_rp() vert.write_attrib(prep + 'wnormal = normalize(N * (vec3(nor.xy, pos.w) + 2.0 * cross(skinA.xyz, cross(skinA.xyz, vec3(nor.xy, pos.w)) + skinA.w * vec3(nor.xy, pos.w))));') diff --git a/blender/arm/material/make_transluc.py b/blender/arm/material/make_transluc.py index 4257b53e..a1ea5162 100644 --- a/blender/arm/material/make_transluc.py +++ b/blender/arm/material/make_transluc.py @@ -1,10 +1,22 @@ import bpy + import arm.material.cycles as cycles import arm.material.mat_state as mat_state import arm.material.make_mesh as make_mesh import arm.material.make_finalize as make_finalize import arm.assets as assets +if "DO_RELOAD_MODULE" in locals(): + import arm + cycles = arm.reload_module(cycles) + mat_state = arm.reload_module(mat_state) + make_mesh = arm.reload_module(make_mesh) + make_finalize = arm.reload_module(make_finalize) + assets = arm.reload_module(assets) +else: + DO_RELOAD_MODULE = True + + def make(context_id): con_transluc = mat_state.data.add_context({ 'name': context_id, 'depth_write': False, 'compare_mode': 'less', 'cull_mode': 'clockwise', \ 'blend_source': 'blend_one', 'blend_destination': 'blend_one', 'blend_operation': 'add', \ @@ -26,7 +38,7 @@ def make(context_id): if '_VoxelAOvar' in wrd.world_defs: frag.write('indirect *= 0.25;') frag.write('vec4 premultipliedReflect = vec4(vec3(direct + indirect * 0.5) * opacity, opacity);') - + frag.write('float w = clamp(pow(min(1.0, premultipliedReflect.a * 10.0) + 0.01, 3.0) * 1e8 * pow(1.0 - (gl_FragCoord.z) * 0.9, 3.0), 1e-2, 3e3);') frag.write('fragColor[0] = vec4(premultipliedReflect.rgb * w, premultipliedReflect.a);') frag.write('fragColor[1] = vec4(premultipliedReflect.a * w, 0.0, 0.0, 1.0);') diff --git a/blender/arm/material/make_voxel.py b/blender/arm/material/make_voxel.py index d86fac29..90dad58d 100644 --- a/blender/arm/material/make_voxel.py +++ b/blender/arm/material/make_voxel.py @@ -1,11 +1,16 @@ import bpy + import arm.utils import arm.assets as assets -import arm.material.cycles as cycles import arm.material.mat_state as mat_state -import arm.material.mat_utils as mat_utils -import arm.material.make_particle as make_particle -import arm.make_state as state + +if "DO_RELOAD_MODULE" in locals(): + arm.utils = arm.reload_module(arm.utils) + assets = arm.reload_module(assets) + mat_state = arm.reload_module(mat_state) +else: + DO_RELOAD_MODULE = True + def make(context_id): rpdat = arm.utils.get_rp() @@ -108,7 +113,7 @@ def make_ao(context_id): if rpdat.arm_voxelgi_revoxelize and rpdat.arm_voxelgi_camera: vert.add_uniform('vec3 eyeSnap', '_cameraPositionSnap') vert.write('voxpositionGeom = (vec3(W * vec4(pos.xyz, 1.0)) - eyeSnap) / voxelgiHalfExtents;') - else: + else: vert.write('voxpositionGeom = vec3(W * vec4(pos.xyz, 1.0)) / voxelgiHalfExtents;') geom.add_out('vec3 voxposition') diff --git a/blender/arm/material/mat_batch.py b/blender/arm/material/mat_batch.py index dc86eefe..fca06750 100644 --- a/blender/arm/material/mat_batch.py +++ b/blender/arm/material/mat_batch.py @@ -1,8 +1,15 @@ -import bpy import arm.material.cycles as cycles import arm.material.make_shader as make_shader import arm.material.mat_state as mat_state +if "DO_RELOAD_MODULE" in locals(): + import arm + cycles = arm.reload_module(cycles) + make_shader = arm.reload_module(make_shader) + mat_state = arm.reload_module(mat_state) +else: + DO_RELOAD_MODULE = True + # TODO: handle groups # TODO: handle cached shaders @@ -21,7 +28,7 @@ def traverse_tree(node, sign): def get_signature(mat): nodes = mat.node_tree.nodes output_node = cycles.node_by_type(nodes, 'OUTPUT_MATERIAL') - + if output_node != None: sign = traverse_tree(output_node, '') # Append flags @@ -40,7 +47,7 @@ def traverse_tree2(node, ar): def get_sorted(mat): nodes = mat.node_tree.nodes output_node = cycles.node_by_type(nodes, 'OUTPUT_MATERIAL') - + if output_node != None: ar = [] traverse_tree2(output_node, ar) diff --git a/blender/arm/material/mat_utils.py b/blender/arm/material/mat_utils.py index b1567a72..497061d8 100644 --- a/blender/arm/material/mat_utils.py +++ b/blender/arm/material/mat_utils.py @@ -1,9 +1,18 @@ import bpy + import arm.utils import arm.make_state as make_state import arm.material.cycles as cycles import arm.log as log +if "DO_RELOAD_MODULE" in locals(): + arm.utils = arm.reload_module(arm.utils) + make_state = arm.reload_module(make_state) + cycles = arm.reload_module(cycles) + log = arm.reload_module(log) +else: + DO_RELOAD_MODULE = True + add_mesh_contexts = [] def disp_linked(output_node): @@ -42,7 +51,7 @@ def get_rpasses(material): ar.append('voxel') if rpdat.rp_renderer == 'Forward' and rpdat.rp_depthprepass and not material.arm_blending and not material.arm_particle_flag: ar.append('depth') - + if material.arm_cast_shadow and rpdat.rp_shadows and ('mesh' in ar): ar.append('shadowmap') diff --git a/blender/arm/material/parser_state.py b/blender/arm/material/parser_state.py index 8606e63f..6bd5a1b2 100644 --- a/blender/arm/material/parser_state.py +++ b/blender/arm/material/parser_state.py @@ -5,6 +5,13 @@ import bpy from arm.material.shader import Shader, ShaderContext, vec3str, floatstr +if "DO_RELOAD_MODULE" in locals(): + import arm + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import Shader, ShaderContext, vec3str, floatstr +else: + DO_RELOAD_MODULE = True + class ParserContext(Enum): """Describes which kind of node tree is parsed.""" diff --git a/blender/arm/material/shader.py b/blender/arm/material/shader.py index 8e107628..afddb2f1 100644 --- a/blender/arm/material/shader.py +++ b/blender/arm/material/shader.py @@ -1,5 +1,10 @@ import arm.utils +if "DO_RELOAD_MODULE" in locals(): + arm.utils = arm.reload_module(arm.utils) +else: + DO_RELOAD_MODULE = True + # Type aliases for type hints to make it easier to see which kind of # shader data type is stored in a string floatstr = str diff --git a/blender/arm/node_utils.py b/blender/arm/node_utils.py index ef465b32..339aef1c 100755 --- a/blender/arm/node_utils.py +++ b/blender/arm/node_utils.py @@ -10,6 +10,13 @@ import arm.log import arm.logicnode.arm_sockets import arm.utils +if "DO_RELOAD_MODULE" in locals(): + arm.log = arm.reload_module(arm.log) + arm.logicnode.arm_sockets = arm.reload_module(arm.logicnode.arm_sockets) + arm.utils = arm.reload_module(arm.utils) +else: + DO_RELOAD_MODULE = True + def find_node_by_link(node_group, to_node, inp): for link in node_group.links: diff --git a/blender/arm/nodes_logic.py b/blender/arm/nodes_logic.py index 87b73869..d266f920 100755 --- a/blender/arm/nodes_logic.py +++ b/blender/arm/nodes_logic.py @@ -11,6 +11,16 @@ import arm.props_traits import arm.ui_icons as ui_icons import arm.utils +if "DO_RELOAD_MODULE" in locals(): + arm_nodes = arm.reload_module(arm_nodes) + arm.logicnode.replacement = arm.reload_module(arm.logicnode.replacement) + arm.logicnode = arm.reload_module(arm.logicnode) + arm.props_traits = arm.reload_module(arm.props_traits) + ui_icons = arm.reload_module(ui_icons) + arm.utils = arm.reload_module(arm.utils) +else: + DO_RELOAD_MODULE = True + registered_nodes = [] registered_categories = [] diff --git a/blender/arm/nodes_material.py b/blender/arm/nodes_material.py index ef9a8f1e..04bfd8a7 100644 --- a/blender/arm/nodes_material.py +++ b/blender/arm/nodes_material.py @@ -7,6 +7,14 @@ import arm.material.arm_nodes.arm_nodes as arm_nodes # even if it looks unused from arm.material.arm_nodes import * +if "DO_RELOAD_MODULE" in locals(): + import arm + arm_nodes = arm.reload_module(arm_nodes) + arm.material.arm_nodes = arm.reload_module(arm.material.arm_nodes) + from arm.material.arm_nodes import * +else: + DO_RELOAD_MODULE = True + registered_nodes = [] diff --git a/blender/arm/profiler.py b/blender/arm/profiler.py index 87f7a0a2..ca96b70f 100644 --- a/blender/arm/profiler.py +++ b/blender/arm/profiler.py @@ -5,6 +5,13 @@ import pstats import arm.log as log import arm.utils as utils +if "DO_RELOAD_MODULE" in locals(): + import arm + log = arm.reload_module(log) + utils = arm.reload_module(utils) +else: + DO_RELOAD_MODULE = True + class Profile: """Context manager for profiling the enclosed code when the given condition is true. diff --git a/blender/arm/props.py b/blender/arm/props.py index 8f3d9af2..80d8fb5c 100755 --- a/blender/arm/props.py +++ b/blender/arm/props.py @@ -10,6 +10,16 @@ import arm.nodes_logic import arm.proxy import arm.utils +if "DO_RELOAD_MODULE" in locals(): + assets = arm.reload_module(assets) + arm.logicnode.replacement = arm.reload_module(arm.logicnode.replacement) + arm.make = arm.reload_module(arm.make) + arm.nodes_logic = arm.reload_module(arm.nodes_logic) + arm.proxy = arm.reload_module(arm.proxy) + arm.utils = arm.reload_module(arm.utils) +else: + DO_RELOAD_MODULE = True + # Armory version arm_version = '2021.7' arm_commit = '$Id$' diff --git a/blender/arm/props_bake.py b/blender/arm/props_bake.py index e433e682..b096b36d 100644 --- a/blender/arm/props_bake.py +++ b/blender/arm/props_bake.py @@ -1,10 +1,19 @@ -import arm.utils -import arm.assets import bpy from bpy.types import Menu, Panel, UIList from bpy.props import * + from arm.lightmapper import operators, properties, utility +import arm.assets +import arm.utils + +if "DO_RELOAD_MODULE" in locals(): + arm.assets = arm.reload_module(arm.assets) + arm.utils = arm.reload_module(arm.utils) +else: + DO_RELOAD_MODULE = True + + class ArmBakeListItem(bpy.types.PropertyGroup): obj: PointerProperty(type=bpy.types.Object, description="The object to bake") res_x: IntProperty(name="X", description="Texture resolution", default=1024) @@ -166,7 +175,7 @@ class ArmBakeButton(bpy.types.Operator): img_node.image = img img_node.select = True nodes.active = img_node - + obs = bpy.context.view_layer.objects # Unwrap @@ -379,4 +388,4 @@ def unregister(): #Unregister lightmapper operators.unregister() - properties.unregister() \ No newline at end of file + properties.unregister() diff --git a/blender/arm/props_exporter.py b/blender/arm/props_exporter.py index 0dc2f143..775489c2 100644 --- a/blender/arm/props_exporter.py +++ b/blender/arm/props_exporter.py @@ -1,14 +1,23 @@ import os import shutil -import arm.assets as assets -import arm.utils -import bpy import stat import subprocess import webbrowser + +import bpy from bpy.types import Menu, Panel, UIList from bpy.props import * +import arm.assets as assets +import arm.utils + +if "DO_RELOAD_MODULE" in locals(): + assets = arm.reload_module(assets) + arm.utils = arm.reload_module(arm.utils) +else: + DO_RELOAD_MODULE = True + + def remove_readonly(func, path, excinfo): os.chmod(path, stat.S_IWRITE) func(path) @@ -417,7 +426,7 @@ def register(): bpy.types.World.arm_exporterlist = CollectionProperty(type=ArmExporterListItem) bpy.types.World.arm_exporterlist_index = IntProperty(name="Index for my_list", default=0) bpy.types.World.arm_exporter_android_permission_list = CollectionProperty(type=ArmExporterAndroidPermissionListItem) - bpy.types.World.arm_exporter_android_permission_list_index = IntProperty(name="Index for my_list", default=0) + bpy.types.World.arm_exporter_android_permission_list_index = IntProperty(name="Index for my_list", default=0) bpy.types.World.arm_exporter_android_abi_list = CollectionProperty(type=ArmExporterAndroidAbiListItem) bpy.types.World.arm_exporter_android_abi_list_index = IntProperty(name="Index for my_list", default=0) diff --git a/blender/arm/props_lod.py b/blender/arm/props_lod.py index 44bfdca9..2ad67bf2 100755 --- a/blender/arm/props_lod.py +++ b/blender/arm/props_lod.py @@ -1,5 +1,4 @@ import bpy -from bpy.types import Menu, Panel, UIList from bpy.props import * def update_size_prop(self, context): diff --git a/blender/arm/props_renderpath.py b/blender/arm/props_renderpath.py index cd7db3ea..08aa947a 100644 --- a/blender/arm/props_renderpath.py +++ b/blender/arm/props_renderpath.py @@ -4,6 +4,12 @@ from bpy.props import * import arm.assets as assets import arm.utils +if "DO_RELOAD_MODULE" in locals(): + assets = arm.reload_module(assets) + arm.utils = arm.reload_module(arm.utils) +else: + DO_RELOAD_MODULE = True + atlas_sizes = [ ('256', '256', '256'), ('512', '512', '512'), ('1024', '1024', '1024'), diff --git a/blender/arm/props_traits.py b/blender/arm/props_traits.py index eba08da8..74918ee2 100755 --- a/blender/arm/props_traits.py +++ b/blender/arm/props_traits.py @@ -6,6 +6,7 @@ from typing import Union import webbrowser from bpy.types import NodeTree +from bpy.props import * import bpy.utils.previews import arm.make as make @@ -15,6 +16,17 @@ import arm.ui_icons as ui_icons import arm.utils import arm.write_data as write_data +if "DO_RELOAD_MODULE" in locals(): + 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: + DO_RELOAD_MODULE = True + ICON_HAXE = ui_icons.get_id('haxe') ICON_NODES = 'NODETREE' ICON_CANVAS = 'NODE_COMPOSITING' @@ -103,13 +115,13 @@ class ARM_UL_TraitList(bpy.types.UIList): custom_icon = "NONE" custom_icon_value = 0 if item.type_prop == "Haxe Script": - custom_icon_value = ui_icons.get_id("haxe") + custom_icon_value = ICON_HAXE elif item.type_prop == "WebAssembly": - custom_icon_value = ui_icons.get_id("wasm") + custom_icon_value = ICON_WASM elif item.type_prop == "UI Canvas": custom_icon = "NODE_COMPOSITING" elif item.type_prop == "Bundled Script": - custom_icon_value = ui_icons.get_id("bundle") + custom_icon_value = ICON_BUNDLED elif item.type_prop == "Logic Nodes": custom_icon = 'NODETREE' diff --git a/blender/arm/props_traits_props.py b/blender/arm/props_traits_props.py index 24931612..b65072cf 100644 --- a/blender/arm/props_traits_props.py +++ b/blender/arm/props_traits_props.py @@ -1,6 +1,8 @@ import bpy from bpy.props import * +__all__ = ['ArmTraitPropWarning', 'ArmTraitPropListItem', 'ARM_UL_PropList'] + PROP_TYPE_ICONS = { "String": "SORTALPHA", "Int": "CHECKBOX_DEHLT", diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index 4a37b470..3132880d 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -5,6 +5,8 @@ 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 @@ -20,10 +22,24 @@ import arm.proxy import arm.ui_icons as ui_icons import arm.utils -from arm.lightmapper.utility import icon -from arm.lightmapper.properties.denoiser import oidn, optix -from arm.lightmapper.panels import scene -import importlib +if "DO_RELOAD_MODULE" in locals(): + 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: + DO_RELOAD_MODULE = True class ARM_PT_ObjectPropsPanel(bpy.types.Panel): @@ -213,7 +229,7 @@ class ARM_PT_PhysicsPropsPanel(bpy.types.Panel): 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') diff --git a/blender/arm/ui_icons.py b/blender/arm/ui_icons.py index ed2649f5..9a40b4f0 100644 --- a/blender/arm/ui_icons.py +++ b/blender/arm/ui_icons.py @@ -6,6 +6,19 @@ from typing import Optional import bpy.utils.previews +if "DO_RELOAD_MODULE" in locals(): + # _unload_icons is not available in the module scope yet + def __unload(): + _unload_icons() + + # Refresh icons after reload + __unload() +else: + DO_RELOAD_MODULE = True + + +__all__ = ["get_id"] + _icons_dict: Optional[bpy.utils.previews.ImagePreviewCollection] = None """Dictionary of all loaded icons, or `None` if not loaded""" @@ -13,21 +26,28 @@ _icons_dir = os.path.join(os.path.dirname(__file__), "custom_icons") """Directory of the icon files""" -def _load_icons() -> None: - """(Re)loads all icons""" +def _load_icons(): + """(Re)loads all icons.""" global _icons_dict - if _icons_dict is not None: - bpy.utils.previews.remove(_icons_dict) + _unload_icons() _icons_dict = bpy.utils.previews.new() - _icons_dict.load("bundle", os.path.join(_icons_dir, "bundle.png"), 'IMAGE') - _icons_dict.load("haxe", os.path.join(_icons_dir, "haxe.png"), 'IMAGE') - _icons_dict.load("wasm", os.path.join(_icons_dir, "wasm.png"), 'IMAGE') + _icons_dict.load("bundle", os.path.join(_icons_dir, "bundle.png"), 'IMAGE', force_reload=True) + _icons_dict.load("haxe", os.path.join(_icons_dir, "haxe.png"), 'IMAGE', force_reload=True) + _icons_dict.load("wasm", os.path.join(_icons_dir, "wasm.png"), 'IMAGE', force_reload=True) + + +def _unload_icons(): + """Unloads all icons.""" + global _icons_dict + if _icons_dict is not None: + bpy.utils.previews.remove(_icons_dict) + _icons_dict = None def get_id(identifier: str) -> int: - """Returns the icon ID from the given identifier""" + """Returns the icon ID from the given identifier.""" if _icons_dict is None: _load_icons() return _icons_dict[identifier].icon_id diff --git a/blender/arm/utils.py b/blender/arm/utils.py index 7a9f1084..40a7c6d6 100755 --- a/blender/arm/utils.py +++ b/blender/arm/utils.py @@ -1,13 +1,14 @@ +from enum import Enum, unique import glob import json +import locale import os import platform import re +import shlex import subprocess from typing import Any, Dict, List, Optional, Tuple import webbrowser -import shlex -import locale import numpy as np @@ -18,7 +19,17 @@ from arm.lib.lz4 import LZ4 import arm.log as log import arm.make_state as state import arm.props_renderpath -from enum import Enum, unique + +if "DO_RELOAD_MODULE" in locals(): + arm.lib.armpack = arm.reload_module(arm.lib.armpack) + arm.lib.lz4 = arm.reload_module(arm.lib.lz4) + from arm.lib.lz4 import LZ4 + log = arm.reload_module(log) + state = arm.reload_module(state) + arm.props_renderpath = arm.reload_module(arm.props_renderpath) +else: + DO_RELOAD_MODULE = True + class NumpyEncoder(json.JSONEncoder): def default(self, obj): diff --git a/blender/arm/write_data.py b/blender/arm/write_data.py index 0eb56dc9..0e0cac42 100755 --- a/blender/arm/write_data.py +++ b/blender/arm/write_data.py @@ -11,6 +11,14 @@ import arm.assets as assets import arm.make_state as state import arm.utils +if "DO_RELOAD_MODULE" in locals(): + import arm + assets = arm.reload_module(assets) + state = arm.reload_module(state) + arm.utils = arm.reload_module(arm.utils) +else: + DO_RELOAD_MODULE = True + def on_same_drive(path1: str, path2: str) -> bool: drive_path1, _ = os.path.splitdrive(path1) diff --git a/blender/arm/write_probes.py b/blender/arm/write_probes.py index f6ca036e..83a55a37 100644 --- a/blender/arm/write_probes.py +++ b/blender/arm/write_probes.py @@ -11,6 +11,14 @@ import arm.assets as assets import arm.log as log import arm.utils +if "DO_RELOAD_MODULE" in locals(): + import arm + assets = arm.reload_module(assets) + log = arm.reload_module(log) + arm.utils = arm.reload_module(arm.utils) +else: + DO_RELOAD_MODULE = True + def add_irr_assets(output_file_irr): assets.add(output_file_irr + '.arm') diff --git a/blender/start.py b/blender/start.py index d53cc4b3..19d75bd2 100755 --- a/blender/start.py +++ b/blender/start.py @@ -1,3 +1,7 @@ +import time + +import arm +import arm.log import arm.nodes_logic import arm.nodes_material import arm.props_traits_props @@ -15,8 +19,38 @@ import arm.handlers import arm.utils import arm.keymap +reload_started = 0 + +if "DO_RELOAD_MODULE" in locals(): + arm.log.debug('Reloading Armory SDK...') + reload_started = time.time() + + # Clear the module cache + import importlib + arm = importlib.reload(arm) # type: ignore + + arm.nodes_logic = arm.reload_module(arm.nodes_logic) + arm.nodes_material = arm.reload_module(arm.nodes_material) + arm.props_traits_props = arm.reload_module(arm.props_traits_props) + arm.props_traits = arm.reload_module(arm.props_traits) + arm.props_lod = arm.reload_module(arm.props_lod) + arm.props_tilesheet = arm.reload_module(arm.props_tilesheet) + arm.props_exporter = arm.reload_module(arm.props_exporter) + arm.props_bake = arm.reload_module(arm.props_bake) + arm.props_renderpath = arm.reload_module(arm.props_renderpath) + arm.props_properties = arm.reload_module(arm.props_properties) + arm.props_collision_filter_mask = arm.reload_module(arm.props_collision_filter_mask) + arm.props = arm.reload_module(arm.props) + arm.props_ui = arm.reload_module(arm.props_ui) + arm.handlers = arm.reload_module(arm.handlers) + arm.utils = arm.reload_module(arm.utils) + arm.keymap = arm.reload_module(arm.keymap) +else: + DO_RELOAD_MODULE = True + registered = False + def register(local_sdk=False): global registered registered = True @@ -37,6 +71,10 @@ def register(local_sdk=False): arm.handlers.register() arm.props_collision_filter_mask.register() + if reload_started != 0: + arm.log.debug(f'Armory SDK: Reloading finished in {time.time() - reload_started:.3f}s') + + def unregister(): global registered registered = False From 4c4bebcf4f80c8ec467c3affd46b58ed2260c4a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Wed, 4 Aug 2021 23:35:54 +0200 Subject: [PATCH 256/264] Canvas: fix using non-default fonts --- Sources/armory/trait/internal/CanvasScript.hx | 19 +++++++++++++------ Sources/armory/ui/Canvas.hx | 2 +- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Sources/armory/trait/internal/CanvasScript.hx b/Sources/armory/trait/internal/CanvasScript.hx index 21dab963..607f4cfa 100644 --- a/Sources/armory/trait/internal/CanvasScript.hx +++ b/Sources/armory/trait/internal/CanvasScript.hx @@ -40,20 +40,27 @@ class CanvasScript extends Trait { Canvas.themes.push(armory.ui.Themes.light); } - iron.data.Data.getFont(font, function(f: kha.Font) { + iron.data.Data.getFont(font, function(defaultFont: kha.Font) { var c: TCanvas = haxe.Json.parse(blob.toString()); if (c.theme == null) c.theme = Canvas.themes[0].NAME; - cui = new Zui({font: f, theme: Canvas.getTheme(c.theme)}); + cui = new Zui({font: defaultFont, theme: Canvas.getTheme(c.theme)}); if (c.assets == null || c.assets.length == 0) canvas = c; else { // Load canvas assets var loaded = 0; for (asset in c.assets) { var file = asset.name; - iron.data.Data.getImage(file, function(image: kha.Image) { - Canvas.assetMap.set(asset.id, image); - if (++loaded >= c.assets.length) canvas = c; - }); + if (Canvas.isFontAsset(file)) { + iron.data.Data.getFont(file, function(f: kha.Font) { + Canvas.assetMap.set(asset.id, f); + if (++loaded >= c.assets.length) canvas = c; + }); + } else { + iron.data.Data.getImage(file, function(image: kha.Image) { + Canvas.assetMap.set(asset.id, image); + if (++loaded >= c.assets.length) canvas = c; + }); + } } } }); diff --git a/Sources/armory/ui/Canvas.hx b/Sources/armory/ui/Canvas.hx index dc798adc..a676e5f9 100644 --- a/Sources/armory/ui/Canvas.hx +++ b/Sources/armory/ui/Canvas.hx @@ -258,7 +258,7 @@ class Canvas { return Std.int(f * _ui.SCALE()); } - static inline function isFontAsset(assetName: Null): Bool { + public static inline function isFontAsset(assetName: Null): Bool { return assetName != null && StringTools.endsWith(assetName.toLowerCase(), ".ttf"); } From 386d2a3bf411f93c04c6b7fad00127cc1039cd20 Mon Sep 17 00:00:00 2001 From: Lubos Lenco Date: Fri, 6 Aug 2021 11:10:28 +0200 Subject: [PATCH 257/264] Bump version --- blender/arm/props.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blender/arm/props.py b/blender/arm/props.py index 8f3d9af2..7adebe05 100755 --- a/blender/arm/props.py +++ b/blender/arm/props.py @@ -11,7 +11,7 @@ import arm.proxy import arm.utils # Armory version -arm_version = '2021.7' +arm_version = '2021.8' arm_commit = '$Id$' def get_project_html5_copy(self): From 2d3e15064a342f0b0d479bad92e674334b3626df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Fri, 6 Aug 2021 22:33:20 +0200 Subject: [PATCH 258/264] Fix armsdk CI Updated Logic.hx with changes made in make_logic.py. Is this file even used somewhere? --- Sources/armory/system/Logic.hx | 59 ++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/Sources/armory/system/Logic.hx b/Sources/armory/system/Logic.hx index f7e4c442..e71390fa 100644 --- a/Sources/armory/system/Logic.hx +++ b/Sources/armory/system/Logic.hx @@ -94,6 +94,10 @@ class Logic { var v = createClassInstance(node.type, [tree]); nodeMap.set(name, v); + #if arm_patch + tree.nodes.set(name, v); + #end + // Properties for (i in 0...5) { for (b in node.buttons) { @@ -103,9 +107,13 @@ class Logic { } } + @:privateAccess v.preallocInputs(node.inputs.length); + @:privateAccess v.preallocOutputs(node.outputs.length); + // Create inputs var inp_node: armory.logicnode.LogicNode = null; var inp_from = 0; + var from_type: String; for (i in 0...node.inputs.length) { var inp = node.inputs[i]; // Is linked - find node @@ -117,6 +125,7 @@ class Logic { for (i in 0...n.outputs.length) { if (n.outputs[i] == socket) { inp_from = i; + from_type = socket.type; break; } } @@ -124,27 +133,33 @@ class Logic { else { // Not linked - create node with default values inp_node = build_default_node(inp); inp_from = 0; + from_type = inp.type; } // Add input - v.addInput(inp_node, inp_from); + var link = LogicNode.addLink(inp_node, v, inp_from, i); + #if arm_patch + link.fromType = from_type; + link.toType = inp.type; + link.toValue = getSocketDefaultValue(inp); + #end } // Create outputs - for (out in node.outputs) { - var outNodes: Array = []; + for (i in 0...node.outputs.length) { + var out = node.outputs[i]; var ls = getOutputLinks(out); - if (ls != null && ls.length > 0) { - for (l in ls) { - var n = getNode(l.to_id); - var out_name = build_node(n); - outNodes.push(nodeMap.get(out_name)); - } + + // Linked outputs are already handled after iterating over inputs + // above, so only unconnected outputs are handled here + if (ls == null || ls.length == 0) { + var link = LogicNode.addLink(v, build_default_node(out), i, 0); + + #if arm_patch + link.fromType = out.type; + link.toType = out.type; + link.toValue = getSocketDefaultValue(out); + #end } - else { // Not linked - create node with default values - outNodes.push(build_default_node(out)); - } - // Add outputs - v.addOutputs(outNodes); } return name; @@ -212,6 +227,22 @@ class Logic { return v; } + static function getSocketDefaultValue(socket: TNodeSocket): Any { + + var v: armory.logicnode.LogicNode = null; + + return switch (socket.type) { + case "OBJECT" | "VALUE" | "INT" | "BOOLEAN" | "STRING": + socket.default_value; + case "VECTOR" | "RGB": + socket.default_value == null ? [0, 0, 0] : [socket.default_value[0], socket.default_value[1], socket.default_value[2]]; + case "RGBA": + socket.default_value == null ? [0, 0, 0, 1] : [socket.default_value[0], socket.default_value[1], socket.default_value[2], socket.default_value[3]]; + default: + null; + } + } + static function createClassInstance(className: String, args: Array): Dynamic { var cname = Type.resolveClass(packageName + "." + className); if (cname == null) return null; From 5509096158cf92a0afac26d92f0f46e8b5afd18f Mon Sep 17 00:00:00 2001 From: N8n5h Date: Fri, 6 Aug 2021 18:34:39 -0300 Subject: [PATCH 259/264] fix volumetric lights not working with shadow map atlas enabled Added missing code so the shader refers to the correct variable when using shadow map atlasing. --- .../volumetric_light.frag.glsl | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Shaders/volumetric_light/volumetric_light.frag.glsl b/Shaders/volumetric_light/volumetric_light.frag.glsl index a0817b54..693d7b1d 100644 --- a/Shaders/volumetric_light/volumetric_light.frag.glsl +++ b/Shaders/volumetric_light/volumetric_light.frag.glsl @@ -44,7 +44,15 @@ uniform vec2 cameraPlane; uniform vec3 sunDir; uniform vec3 sunCol; #ifdef _ShadowMap + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + uniform sampler2DShadow shadowMapAtlasSun; + #else + uniform sampler2DShadow shadowMapAtlas; + #endif + #else uniform sampler2DShadow shadowMap; + #endif uniform float shadowsBias; #ifdef _CSM //!uniform vec4 casData[shadowmapCascades * 4 + 4]; @@ -95,7 +103,17 @@ void rayStep(inout vec3 curPos, inout float curOpticalDepth, inout float scatter #endif vec4 lPos = LWVP * vec4(curPos, 1.0); lPos.xyz /= lPos.w; - visibility = texture(shadowMap, vec3(lPos.xy, lPos.z - shadowsBias)); + visibility = texture( + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + shadowMapAtlasSun + #else + shadowMapAtlas + #endif + #else + shadowMap + #endif + , vec3(lPos.xy, lPos.z - shadowsBias)); #endif #ifdef _SinglePoint From 34f6e0fc7c6b91d3d99770eead225c104031f37f Mon Sep 17 00:00:00 2001 From: Lubos Lenco Date: Sun, 8 Aug 2021 13:02:19 +0200 Subject: [PATCH 260/264] Kha update --- Sources/armory/trait/internal/CanvasScript.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/armory/trait/internal/CanvasScript.hx b/Sources/armory/trait/internal/CanvasScript.hx index 607f4cfa..92c585e1 100644 --- a/Sources/armory/trait/internal/CanvasScript.hx +++ b/Sources/armory/trait/internal/CanvasScript.hx @@ -29,7 +29,7 @@ class CanvasScript extends Trait { iron.data.Data.getBlob(canvasName + ".json", function(blob: kha.Blob) { iron.data.Data.getBlob("_themes.json", function(tBlob: kha.Blob) { - if (tBlob.get_length() != 0) { + if (@:privateAccess tBlob.get_length() != 0) { Canvas.themes = haxe.Json.parse(tBlob.toString()); } else { From d610cc6a2fa848aaf380406df235ce213233f446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Mon, 9 Aug 2021 16:51:26 +0200 Subject: [PATCH 261/264] Fix value range in Nishita LUT --- Shaders/std/sky.glsl | 24 ++++++++---------- Sources/armory/renderpath/Nishita.hx | 38 +++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/Shaders/std/sky.glsl b/Shaders/std/sky.glsl index b4465351..e192bb11 100644 --- a/Shaders/std/sky.glsl +++ b/Shaders/std/sky.glsl @@ -44,17 +44,12 @@ uniform vec2 nishitaDensity; #define nishita_mie_dir 0.76 // Aerosols anisotropy ("direction") #define nishita_mie_dir_sq 0.5776 // Squared aerosols anisotropy -// The ozone absorption coefficients are taken from Cycles code. -// Because Cycles calculates 21 wavelengths, we use the coefficients -// which are closest to the RGB wavelengths (645nm, 510nm, 440nm). -// Precalculating values by simulating Blender's spec_to_xyz() function -// to include all 21 wavelengths gave unrealistic results -#define nishita_ozone_coeff vec3(1.59051840791988e-6, 0.00000096707041180970, 0.00000007309568762914) // Values from [Hill: 60] #define sun_limb_darkening_col vec3(0.397, 0.503, 0.652) float random(vec2 coords) { + // Returned value is in [0, 1] return fract(sin(dot(coords.xy, vec2(12.9898,78.233))) * 43758.5453); } @@ -125,17 +120,18 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa // Idea behind this: "Rotate" everything by iPos (-> iPos is the new zenith) and then all calculations for the // inner integral only depend on the sample height (iHeight) and sunTheta (angle between sun and new zenith). float sunTheta = acos(dot(normalize(iPos), normalize(pSun))); - vec3 jODepth = nishita_lookupLUT(iHeight, sunTheta); - - // Apply dithering to reduce visible banding - jODepth += mix(-1000, 1000, random(r.xy)); + vec3 jAttn = nishita_lookupLUT(iHeight, sunTheta); // Calculate attenuation - vec3 attn = exp(-( - nishita_mie_coeff * (iOdMie + jODepth.y) - + (nishita_rayleigh_coeff) * (iOdRlh + jODepth.x) - + nishita_ozone_coeff * jODepth.z + vec3 iAttn = exp(-( + nishita_mie_coeff * iOdMie + + nishita_rayleigh_coeff * iOdRlh + // + 0 for ozone )); + vec3 attn = iAttn * jAttn; + + // Apply dithering to reduce visible banding + attn *= 0.98 + random(r.xy) * 0.04; // Accumulate scattering totalRlh += odStepRlh * attn; diff --git a/Sources/armory/renderpath/Nishita.hx b/Sources/armory/renderpath/Nishita.hx index 7dcba4a2..177cb517 100644 --- a/Sources/armory/renderpath/Nishita.hx +++ b/Sources/armory/renderpath/Nishita.hx @@ -80,11 +80,24 @@ class NishitaData { **/ public static var radiusPlanet = 6360000; + /** Rayleigh scattering coefficient. **/ + public static var rayleighCoeff = new Vec3(5.5e-6, 13.0e-6, 22.4e-6); /** Rayleigh scattering scale parameter. **/ public static var rayleighScale = 8e3; + + /** Mie scattering coefficient. **/ + public static var mieCoeff = 2e-5; /** Mie scattering scale parameter. **/ public static var mieScale = 1.2e3; + /** Ozone scattering coefficient. **/ + // The ozone absorption coefficients are taken from Cycles code. + // Because Cycles calculates 21 wavelengths, we use the coefficients + // which are closest to the RGB wavelengths (645nm, 510nm, 440nm). + // Precalculating values by simulating Blender's spec_to_xyz() function + // to include all 21 wavelengths gave unrealistic results. + public static var ozoneCoeff = new Vec3(1.59051840791988e-6, 0.00000096707041180970, 0.00000007309568762914); + public function new() {} /** Approximates the density of ozone for a given sample height. **/ @@ -185,6 +198,29 @@ class NishitaData { jTime += jStepSize; } - return jODepth.mult(jStepSize); + jODepth.mult(jStepSize); + + // Precalculate a part of the secondary attenuation. + // For one variable (e.g. x) in the vector, the formula is as follows: + // + // attn.x = exp(-(coeffX * (firstOpticalDepth.x + secondOpticalDepth.x))) + // + // We can split that up via: + // + // attn.x = exp(-(coeffX * firstOpticalDepth.x + coeffX * secondOpticalDepth.x)) + // = exp(-(coeffX * firstOpticalDepth.x)) * exp(-(coeffX * secondOpticalDepth.x)) + // + // The first factor of the resulting multiplication is calculated in the + // shader, but we can already precalculate the second one. As a side + // effect this keeps the range of the LUT values small because we don't + // store the optical depth but the attenuation. + var jAttenuation = new Vec3(); + var mie = mieCoeff * jODepth.y; + jAttenuation.addf(mie, mie, mie); + jAttenuation.add(rayleighCoeff.clone().mult(jODepth.x)); + jAttenuation.add(ozoneCoeff.clone().mult(jODepth.z)); + jAttenuation.exp(jAttenuation.mult(-1)); + + return jAttenuation; } } From c56a0c3a728e53f727a049289cdf3af649a55b20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Wed, 11 Aug 2021 14:32:21 +0200 Subject: [PATCH 262/264] Fix accidental module reloading caused by name conflicts of global vars --- blender/arm/__init__.py | 18 ++++++++++++++++++ blender/arm/api.py | 6 +++--- blender/arm/assets.py | 4 ++-- blender/arm/exporter.py | 4 ++-- blender/arm/exporter_opt.py | 4 ++-- blender/arm/handlers.py | 5 +++-- blender/arm/keymap.py | 6 +++--- blender/arm/live_patch.py | 4 ++-- blender/arm/logicnode/__init__.py | 4 ++-- blender/arm/logicnode/arm_nodes.py | 4 ++-- blender/arm/logicnode/arm_sockets.py | 4 ++-- blender/arm/logicnode/replacement.py | 4 ++-- blender/arm/make.py | 4 ++-- blender/arm/make_logic.py | 4 ++-- blender/arm/make_renderpath.py | 4 ++-- blender/arm/make_state.py | 6 ++++-- blender/arm/make_world.py | 4 ++-- .../material/arm_nodes/custom_particle_node.py | 4 ++-- .../arm/material/arm_nodes/shader_data_node.py | 6 +++--- blender/arm/material/cycles.py | 4 ++-- .../arm/material/cycles_nodes/nodes_color.py | 6 +++--- .../material/cycles_nodes/nodes_converter.py | 6 +++--- .../arm/material/cycles_nodes/nodes_input.py | 4 ++-- .../arm/material/cycles_nodes/nodes_shader.py | 6 +++--- .../arm/material/cycles_nodes/nodes_texture.py | 4 ++-- .../arm/material/cycles_nodes/nodes_vector.py | 6 +++--- blender/arm/material/make.py | 4 ++-- blender/arm/material/make_attrib.py | 4 ++-- blender/arm/material/make_decal.py | 4 ++-- blender/arm/material/make_depth.py | 4 ++-- blender/arm/material/make_finalize.py | 6 +++--- blender/arm/material/make_mesh.py | 4 ++-- blender/arm/material/make_overlay.py | 6 +++--- blender/arm/material/make_particle.py | 4 ++-- blender/arm/material/make_shader.py | 4 ++-- blender/arm/material/make_skin.py | 4 ++-- blender/arm/material/make_transluc.py | 6 +++--- blender/arm/material/make_voxel.py | 4 ++-- blender/arm/material/mat_batch.py | 6 +++--- blender/arm/material/mat_utils.py | 4 ++-- blender/arm/material/parser_state.py | 6 +++--- blender/arm/material/shader.py | 4 ++-- blender/arm/node_utils.py | 4 ++-- blender/arm/nodes_logic.py | 4 ++-- blender/arm/nodes_material.py | 6 +++--- blender/arm/profiler.py | 6 +++--- blender/arm/props.py | 4 ++-- blender/arm/props_bake.py | 4 ++-- blender/arm/props_exporter.py | 4 ++-- blender/arm/props_renderpath.py | 4 ++-- blender/arm/props_traits.py | 4 ++-- blender/arm/props_ui.py | 5 +++-- blender/arm/ui_icons.py | 6 ++++-- blender/arm/utils.py | 4 ++-- blender/arm/write_data.py | 4 ++-- blender/arm/write_probes.py | 4 ++-- blender/start.py | 4 ++-- 57 files changed, 150 insertions(+), 126 deletions(-) diff --git a/blender/arm/__init__.py b/blender/arm/__init__.py index eb89c678..5937ac15 100755 --- a/blender/arm/__init__.py +++ b/blender/arm/__init__.py @@ -1,10 +1,28 @@ import importlib +import sys import types # This gets cleared if this package/the __init__ module is reloaded _module_cache: dict[str, types.ModuleType] = {} +def enable_reload(module_name: str): + """Enable reloading for the next time the module with `module_name` + is executed. + """ + mod = sys.modules[module_name] + setattr(mod, module_name.replace('.', '_') + "_DO_RELOAD_MODULE", True) + + +def is_reload(module_name: str) -> bool: + """True if the module given by `module_name` should reload the + modules it imports. This is the case if `enable_reload()` was called + for the module before. + """ + mod = sys.modules[module_name] + return hasattr(mod, module_name.replace('.', '_') + "_DO_RELOAD_MODULE") + + def reload_module(module: types.ModuleType) -> types.ModuleType: """Wrapper around importlib.reload() to make sure no module is reloaded twice. diff --git a/blender/arm/api.py b/blender/arm/api.py index d66c9b8c..28fbc85a 100644 --- a/blender/arm/api.py +++ b/blender/arm/api.py @@ -2,16 +2,16 @@ from typing import Callable, Dict, Optional from bpy.types import Material, UILayout +import arm from arm.material.shader import ShaderContext -if "DO_RELOAD_MODULE" in locals(): - import arm +if arm.is_reload(__name__): arm.material.shader = arm.reload_module(arm.material.shader) from arm.material.shader import ShaderContext else: drivers: Dict[str, Dict] = dict() - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) def add_driver(driver_name: str, diff --git a/blender/arm/assets.py b/blender/arm/assets.py index 1882818d..23235966 100755 --- a/blender/arm/assets.py +++ b/blender/arm/assets.py @@ -6,11 +6,11 @@ import bpy import arm.log as log import arm.utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): log = arm.reload_module(log) arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) assets = [] reserved_names = ['return.'] diff --git a/blender/arm/exporter.py b/blender/arm/exporter.py index 92d28a2a..9a344e80 100755 --- a/blender/arm/exporter.py +++ b/blender/arm/exporter.py @@ -32,7 +32,7 @@ import arm.material.mat_batch as mat_batch import arm.utils import arm.profiler -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): assets = arm.reload_module(assets) exporter_opt = arm.reload_module(exporter_opt) log = arm.reload_module(log) @@ -43,7 +43,7 @@ if "DO_RELOAD_MODULE" in locals(): arm.utils = arm.reload_module(arm.utils) arm.profiler = arm.reload_module(arm.profiler) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) @unique diff --git a/blender/arm/exporter_opt.py b/blender/arm/exporter_opt.py index 520a83a1..d13b2b53 100644 --- a/blender/arm/exporter_opt.py +++ b/blender/arm/exporter_opt.py @@ -9,11 +9,11 @@ import numpy as np import arm.log as log import arm.utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): log = arm.reload_module(log) arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) class Vertex: diff --git a/blender/arm/handlers.py b/blender/arm/handlers.py index f77e117a..f63ef3bb 100644 --- a/blender/arm/handlers.py +++ b/blender/arm/handlers.py @@ -6,6 +6,7 @@ import sys import bpy from bpy.app.handlers import persistent +import arm import arm.api import arm.live_patch as live_patch import arm.logicnode.arm_nodes as arm_nodes @@ -15,7 +16,7 @@ import arm.make_state as state import arm.props as props import arm.utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): arm.api = arm.reload_module(arm.api) live_patch = arm.reload_module(live_patch) arm_nodes = arm.reload_module(arm_nodes) @@ -25,7 +26,7 @@ if "DO_RELOAD_MODULE" in locals(): props = arm.reload_module(props) arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) @persistent diff --git a/blender/arm/keymap.py b/blender/arm/keymap.py index 40dabd09..099db0f8 100644 --- a/blender/arm/keymap.py +++ b/blender/arm/keymap.py @@ -1,12 +1,12 @@ import bpy +import arm import arm.props_ui as props_ui -if "DO_RELOAD_MODULE" in locals(): - import arm +if arm.is_reload(__name__): props_ui = arm.reload_module(props_ui) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) arm_keymaps = [] diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py index 6a1c9fea..6344a004 100644 --- a/blender/arm/live_patch.py +++ b/blender/arm/live_patch.py @@ -13,7 +13,7 @@ import arm.make_state as state import arm.node_utils import arm.utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): arm.assets = arm.reload_module(arm.assets) arm.exporter = arm.reload_module(arm.exporter) from arm.exporter import ArmoryExporter @@ -25,7 +25,7 @@ if "DO_RELOAD_MODULE" in locals(): arm.node_utils = arm.reload_module(arm.node_utils) arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) patch_id = 0 """Current patch id""" diff --git a/blender/arm/logicnode/__init__.py b/blender/arm/logicnode/__init__.py index aaaceb5d..f8134402 100644 --- a/blender/arm/logicnode/__init__.py +++ b/blender/arm/logicnode/__init__.py @@ -9,7 +9,7 @@ from arm.logicnode.arm_props import * import arm.logicnode.arm_sockets as arm_sockets from arm.logicnode.replacement import NodeReplacement -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): arm_nodes = arm.reload_module(arm_nodes) arm.logicnode.arm_props = arm.reload_module(arm.logicnode.arm_props) from arm.logicnode.arm_props import * @@ -19,7 +19,7 @@ if "DO_RELOAD_MODULE" in locals(): HAS_RELOADED = True else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) def init_categories(): diff --git a/blender/arm/logicnode/arm_nodes.py b/blender/arm/logicnode/arm_nodes.py index 8ab7381e..e775c833 100644 --- a/blender/arm/logicnode/arm_nodes.py +++ b/blender/arm/logicnode/arm_nodes.py @@ -14,14 +14,14 @@ from arm.logicnode.arm_props import * from arm.logicnode.replacement import NodeReplacement import arm.node_utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): arm.logicnode.arm_props = arm.reload_module(arm.logicnode.arm_props) from arm.logicnode.arm_props import * arm.logicnode.replacement = arm.reload_module(arm.logicnode.replacement) from arm.logicnode.replacement import NodeReplacement arm.node_utils = arm.reload_module(arm.node_utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) # When passed as a category to add_node(), this will use the capitalized # name of the package of the node as the category to make renaming diff --git a/blender/arm/logicnode/arm_sockets.py b/blender/arm/logicnode/arm_sockets.py index df36658e..87362f91 100644 --- a/blender/arm/logicnode/arm_sockets.py +++ b/blender/arm/logicnode/arm_sockets.py @@ -4,10 +4,10 @@ from bpy.types import NodeSocket import arm.utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) def _on_update_socket(self, context): diff --git a/blender/arm/logicnode/replacement.py b/blender/arm/logicnode/replacement.py index d0efc6ec..4a710e6b 100644 --- a/blender/arm/logicnode/replacement.py +++ b/blender/arm/logicnode/replacement.py @@ -20,13 +20,13 @@ import arm.logicnode.arm_nodes as arm_nodes import arm.logicnode.arm_sockets import arm.node_utils as node_utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): log = arm.reload_module(log) arm_nodes = arm.reload_module(arm_nodes) arm.logicnode.arm_sockets = arm.reload_module(arm.logicnode.arm_sockets) node_utils = arm.reload_module(node_utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) # List of errors that occurred during the replacement # Format: (error identifier, node.bl_idname (or None), tree name, exception traceback (optional)) diff --git a/blender/arm/make.py b/blender/arm/make.py index cc599c29..b21770aa 100755 --- a/blender/arm/make.py +++ b/blender/arm/make.py @@ -27,7 +27,7 @@ import arm.make_world as make_world import arm.utils import arm.write_data as write_data -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): assets = arm.reload_module(assets) arm.exporter = arm.reload_module(arm.exporter) from arm.exporter import ArmoryExporter @@ -42,7 +42,7 @@ if "DO_RELOAD_MODULE" in locals(): arm.utils = arm.reload_module(arm.utils) write_data = arm.reload_module(write_data) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) scripts_mtime = 0 # Monitor source changes profile_time = 0 diff --git a/blender/arm/make_logic.py b/blender/arm/make_logic.py index a4195bb8..13c1ae07 100755 --- a/blender/arm/make_logic.py +++ b/blender/arm/make_logic.py @@ -8,14 +8,14 @@ import arm.log import arm.node_utils import arm.utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): arm.exporter = arm.reload_module(arm.exporter) from arm.exporter import ArmoryExporter arm.log = arm.reload_module(arm.log) arm.node_utils = arm.reload_module(arm.node_utils) arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) parsed_nodes = [] parsed_ids = dict() # Sharing node data diff --git a/blender/arm/make_renderpath.py b/blender/arm/make_renderpath.py index 7b2e0dde..d5c7958b 100755 --- a/blender/arm/make_renderpath.py +++ b/blender/arm/make_renderpath.py @@ -6,14 +6,14 @@ import arm.log as log import arm.make_state as state import arm.utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): arm.api = arm.reload_module(arm.api) assets = arm.reload_module(assets) log = arm.reload_module(log) state = arm.reload_module(state) arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) callback = None diff --git a/blender/arm/make_state.py b/blender/arm/make_state.py index bf6c9f97..289e3834 100644 --- a/blender/arm/make_state.py +++ b/blender/arm/make_state.py @@ -1,5 +1,7 @@ -if "DO_RELOAD_MODULE" not in locals(): - DO_RELOAD_MODULE = True +import arm + +if not arm.is_reload(__name__): + arm.enable_reload(__name__) redraw_ui = False target = 'krom' diff --git a/blender/arm/make_world.py b/blender/arm/make_world.py index 3c176d06..1c8c3e4b 100755 --- a/blender/arm/make_world.py +++ b/blender/arm/make_world.py @@ -12,7 +12,7 @@ import arm.node_utils as node_utils import arm.utils import arm.write_probes as write_probes -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): arm.assets = arm.reload_module(arm.assets) arm.log = arm.reload_module(arm.log) arm.material = arm.reload_module(arm.material) @@ -25,7 +25,7 @@ if "DO_RELOAD_MODULE" in locals(): arm.utils = arm.reload_module(arm.utils) write_probes = arm.reload_module(write_probes) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) callback = None shader_datas = [] diff --git a/blender/arm/material/arm_nodes/custom_particle_node.py b/blender/arm/material/arm_nodes/custom_particle_node.py index fbb28e78..e09c78e8 100644 --- a/blender/arm/material/arm_nodes/custom_particle_node.py +++ b/blender/arm/material/arm_nodes/custom_particle_node.py @@ -5,7 +5,7 @@ from arm.material.arm_nodes.arm_nodes import add_node from arm.material.shader import Shader from arm.material.cycles import * -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): import arm arm.material.arm_nodes.arm_nodes = arm.reload_module(arm.material.arm_nodes.arm_nodes) from arm.material.arm_nodes.arm_nodes import add_node @@ -14,7 +14,7 @@ if "DO_RELOAD_MODULE" in locals(): arm.material.cycles = arm.reload_module(arm.material.cycles) from arm.material.cycles import * else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) class CustomParticleNode(Node): diff --git a/blender/arm/material/arm_nodes/shader_data_node.py b/blender/arm/material/arm_nodes/shader_data_node.py index ec7d2077..f4ff6a73 100644 --- a/blender/arm/material/arm_nodes/shader_data_node.py +++ b/blender/arm/material/arm_nodes/shader_data_node.py @@ -1,17 +1,17 @@ from bpy.props import * from bpy.types import Node +import arm from arm.material.arm_nodes.arm_nodes import add_node from arm.material.shader import Shader -if "DO_RELOAD_MODULE" in locals(): - import arm +if arm.is_reload(__name__): arm.material.arm_nodes.arm_nodes = arm.reload_module(arm.material.arm_nodes.arm_nodes) from arm.material.arm_nodes.arm_nodes import add_node arm.material.shader = arm.reload_module(arm.material.shader) from arm.material.shader import Shader else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) class ShaderDataNode(Node): diff --git a/blender/arm/material/cycles.py b/blender/arm/material/cycles.py index 5e20f4dd..91c1c47b 100644 --- a/blender/arm/material/cycles.py +++ b/blender/arm/material/cycles.py @@ -30,7 +30,7 @@ from arm.material.parser_state import ParserState, ParserContext from arm.material.shader import Shader, ShaderContext, floatstr, vec3str import arm.utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): arm.assets = arm.reload_module(arm.assets) log = arm.reload_module(log) arm.make_state = arm.reload_module(arm.make_state) @@ -44,7 +44,7 @@ if "DO_RELOAD_MODULE" in locals(): from arm.material.shader import Shader, ShaderContext, floatstr, vec3str arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) # Particle info export diff --git a/blender/arm/material/cycles_nodes/nodes_color.py b/blender/arm/material/cycles_nodes/nodes_color.py index 08a181f3..97048d5f 100644 --- a/blender/arm/material/cycles_nodes/nodes_color.py +++ b/blender/arm/material/cycles_nodes/nodes_color.py @@ -1,13 +1,13 @@ import bpy +import arm import arm.log as log import arm.material.cycles as c import arm.material.cycles_functions as c_functions from arm.material.parser_state import ParserState from arm.material.shader import floatstr, vec3str -if "DO_RELOAD_MODULE" in locals(): - import arm +if arm.is_reload(__name__): log = arm.reload_module(log) c = arm.reload_module(c) c_functions = arm.reload_module(c_functions) @@ -16,7 +16,7 @@ if "DO_RELOAD_MODULE" in locals(): arm.material.shader = arm.reload_module(arm.material.shader) from arm.material.shader import floatstr, vec3str else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) def parse_brightcontrast(node: bpy.types.ShaderNodeBrightContrast, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: diff --git a/blender/arm/material/cycles_nodes/nodes_converter.py b/blender/arm/material/cycles_nodes/nodes_converter.py index 8a241b2d..553fee50 100644 --- a/blender/arm/material/cycles_nodes/nodes_converter.py +++ b/blender/arm/material/cycles_nodes/nodes_converter.py @@ -2,14 +2,14 @@ from typing import Union import bpy +import arm import arm.log as log import arm.material.cycles as c import arm.material.cycles_functions as c_functions from arm.material.parser_state import ParserState from arm.material.shader import floatstr, vec3str -if "DO_RELOAD_MODULE" in locals(): - import arm +if arm.is_reload(__name__): log = arm.reload_module(log) c = arm.reload_module(c) c_functions = arm.reload_module(c_functions) @@ -18,7 +18,7 @@ if "DO_RELOAD_MODULE" in locals(): arm.material.shader = arm.reload_module(arm.material.shader) from arm.material.shader import floatstr, vec3str else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) def parse_maprange(node: bpy.types.ShaderNodeMapRange, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: diff --git a/blender/arm/material/cycles_nodes/nodes_input.py b/blender/arm/material/cycles_nodes/nodes_input.py index 1eb5c5db..6a8b1aca 100644 --- a/blender/arm/material/cycles_nodes/nodes_input.py +++ b/blender/arm/material/cycles_nodes/nodes_input.py @@ -10,7 +10,7 @@ from arm.material.parser_state import ParserState, ParserContext from arm.material.shader import floatstr, vec3str import arm.utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): log = arm.reload_module(log) c = arm.reload_module(c) c_functions = arm.reload_module(c_functions) @@ -20,7 +20,7 @@ if "DO_RELOAD_MODULE" in locals(): from arm.material.shader import floatstr, vec3str arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) def parse_attribute(node: bpy.types.ShaderNodeAttribute, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]: diff --git a/blender/arm/material/cycles_nodes/nodes_shader.py b/blender/arm/material/cycles_nodes/nodes_shader.py index 45fec6be..734674a1 100644 --- a/blender/arm/material/cycles_nodes/nodes_shader.py +++ b/blender/arm/material/cycles_nodes/nodes_shader.py @@ -1,16 +1,16 @@ import bpy from bpy.types import NodeSocket +import arm import arm.material.cycles as c from arm.material.parser_state import ParserState -if "DO_RELOAD_MODULE" in locals(): - import arm +if arm.is_reload(__name__): c = arm.reload_module(c) arm.material.parser_state = arm.reload_module(arm.material.parser_state) from arm.material.parser_state import ParserState else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) def parse_mixshader(node: bpy.types.ShaderNodeMixShader, out_socket: NodeSocket, state: ParserState) -> None: diff --git a/blender/arm/material/cycles_nodes/nodes_texture.py b/blender/arm/material/cycles_nodes/nodes_texture.py index 406dfba1..d79f360a 100644 --- a/blender/arm/material/cycles_nodes/nodes_texture.py +++ b/blender/arm/material/cycles_nodes/nodes_texture.py @@ -13,7 +13,7 @@ from arm.material.shader import floatstr, vec3str import arm.utils import arm.write_probes as write_probes -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): assets = arm.reload_module(assets) log = arm.reload_module(log) c = arm.reload_module(c) @@ -25,7 +25,7 @@ if "DO_RELOAD_MODULE" in locals(): arm.utils = arm.reload_module(arm.utils) write_probes = arm.reload_module(write_probes) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) def parse_tex_brick(node: bpy.types.ShaderNodeTexBrick, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]: diff --git a/blender/arm/material/cycles_nodes/nodes_vector.py b/blender/arm/material/cycles_nodes/nodes_vector.py index 3a40c378..ab72a240 100644 --- a/blender/arm/material/cycles_nodes/nodes_vector.py +++ b/blender/arm/material/cycles_nodes/nodes_vector.py @@ -3,13 +3,13 @@ from typing import Union import bpy from mathutils import Euler, Vector +import arm import arm.material.cycles as c import arm.material.cycles_functions as c_functions from arm.material.parser_state import ParserState from arm.material.shader import floatstr, vec3str -if "DO_RELOAD_MODULE" in locals(): - import arm +if arm.is_reload(__name__): c = arm.reload_module(c) c_functions = arm.reload_module(c_functions) arm.material.parser_state = arm.reload_module(arm.material.parser_state) @@ -17,7 +17,7 @@ if "DO_RELOAD_MODULE" in locals(): arm.material.shader = arm.reload_module(arm.material.shader) from arm.material.shader import floatstr, vec3str else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) def parse_curvevec(node: bpy.types.ShaderNodeVectorCurve, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: diff --git a/blender/arm/material/make.py b/blender/arm/material/make.py index b067c180..079b9d84 100755 --- a/blender/arm/material/make.py +++ b/blender/arm/material/make.py @@ -10,14 +10,14 @@ import arm.material.mat_batch as mat_batch import arm.node_utils import arm.utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): cycles = arm.reload_module(cycles) make_shader = arm.reload_module(make_shader) mat_batch = arm.reload_module(mat_batch) arm.node_utils = arm.reload_module(arm.node_utils) arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) def glsl_value(val): diff --git a/blender/arm/material/make_attrib.py b/blender/arm/material/make_attrib.py index f82a3ab4..b792bad3 100644 --- a/blender/arm/material/make_attrib.py +++ b/blender/arm/material/make_attrib.py @@ -9,7 +9,7 @@ import arm.material.make_tess as make_tess from arm.material.shader import Shader, ShaderContext import arm.utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): cycles = arm.reload_module(cycles) mat_state = arm.reload_module(mat_state) make_skin = arm.reload_module(make_skin) @@ -20,7 +20,7 @@ if "DO_RELOAD_MODULE" in locals(): from arm.material.shader import Shader, ShaderContext arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) def write_vertpos(vert): diff --git a/blender/arm/material/make_decal.py b/blender/arm/material/make_decal.py index c17b1ead..0be96f75 100644 --- a/blender/arm/material/make_decal.py +++ b/blender/arm/material/make_decal.py @@ -5,13 +5,13 @@ import arm.material.mat_state as mat_state import arm.material.make_finalize as make_finalize import arm.utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): cycles = arm.reload_module(cycles) mat_state = arm.reload_module(mat_state) make_finalize = arm.reload_module(make_finalize) arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) def make(context_id): diff --git a/blender/arm/material/make_depth.py b/blender/arm/material/make_depth.py index 4f9115ca..75ec922c 100644 --- a/blender/arm/material/make_depth.py +++ b/blender/arm/material/make_depth.py @@ -11,7 +11,7 @@ import arm.material.make_finalize as make_finalize import arm.assets as assets import arm.utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): cycles = arm.reload_module(cycles) mat_state = arm.reload_module(mat_state) mat_utils = arm.reload_module(mat_utils) @@ -23,7 +23,7 @@ if "DO_RELOAD_MODULE" in locals(): assets = arm.reload_module(assets) arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) def make(context_id, rpasses, shadowmap=False): diff --git a/blender/arm/material/make_finalize.py b/blender/arm/material/make_finalize.py index 6477e55c..7525b517 100644 --- a/blender/arm/material/make_finalize.py +++ b/blender/arm/material/make_finalize.py @@ -1,15 +1,15 @@ import bpy +import arm import arm.material.make_tess as make_tess from arm.material.shader import ShaderContext -if "DO_RELOAD_MODULE" in locals(): - import arm +if arm.is_reload(__name__): make_tess = arm.reload_module(make_tess) arm.material.shader = arm.reload_module(arm.material.shader) from arm.material.shader import ShaderContext else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) def make(con_mesh: ShaderContext): diff --git a/blender/arm/material/make_mesh.py b/blender/arm/material/make_mesh.py index 652e6d91..e654cf1e 100644 --- a/blender/arm/material/make_mesh.py +++ b/blender/arm/material/make_mesh.py @@ -11,7 +11,7 @@ import arm.material.make_finalize as make_finalize import arm.material.make_attrib as make_attrib import arm.utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): assets = arm.reload_module(assets) mat_state = arm.reload_module(mat_state) mat_utils = arm.reload_module(mat_utils) @@ -23,7 +23,7 @@ if "DO_RELOAD_MODULE" in locals(): make_attrib = arm.reload_module(make_attrib) arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) is_displacement = False write_material_attribs = None diff --git a/blender/arm/material/make_overlay.py b/blender/arm/material/make_overlay.py index de0b0b35..679967f1 100644 --- a/blender/arm/material/make_overlay.py +++ b/blender/arm/material/make_overlay.py @@ -1,16 +1,16 @@ +import arm import arm.material.make_finalize as make_finalize import arm.material.make_mesh as make_mesh import arm.material.mat_state as mat_state import arm.material.mat_utils as mat_utils -if "DO_RELOAD_MODULE" in locals(): - import arm +if arm.is_reload(__name__): make_finalize = arm.reload_module(make_finalize) make_mesh = arm.reload_module(make_mesh) mat_state = arm.reload_module(mat_state) mat_utils = arm.reload_module(mat_utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) def make(context_id): diff --git a/blender/arm/material/make_particle.py b/blender/arm/material/make_particle.py index c62d2040..8c71e136 100644 --- a/blender/arm/material/make_particle.py +++ b/blender/arm/material/make_particle.py @@ -1,11 +1,11 @@ import arm.utils import arm.material.mat_state as mat_state -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): arm.utils = arm.reload_module(arm.utils) mat_state = arm.reload_module(mat_state) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) def write(vert, particle_info=None, shadowmap=False): diff --git a/blender/arm/material/make_shader.py b/blender/arm/material/make_shader.py index 87307799..8e95fa5c 100644 --- a/blender/arm/material/make_shader.py +++ b/blender/arm/material/make_shader.py @@ -22,7 +22,7 @@ import arm.material.mat_utils as mat_utils from arm.material.shader import Shader, ShaderContext, ShaderData import arm.utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): arm.api = arm.reload_module(arm.api) assets = arm.reload_module(assets) arm.exporter = arm.reload_module(arm.exporter) @@ -40,7 +40,7 @@ if "DO_RELOAD_MODULE" in locals(): from arm.material.shader import Shader, ShaderContext, ShaderData arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) rpass_hook = None diff --git a/blender/arm/material/make_skin.py b/blender/arm/material/make_skin.py index 8730b089..aaaff3ce 100644 --- a/blender/arm/material/make_skin.py +++ b/blender/arm/material/make_skin.py @@ -1,9 +1,9 @@ import arm.utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) def skin_pos(vert): diff --git a/blender/arm/material/make_transluc.py b/blender/arm/material/make_transluc.py index a1ea5162..69757345 100644 --- a/blender/arm/material/make_transluc.py +++ b/blender/arm/material/make_transluc.py @@ -1,20 +1,20 @@ import bpy +import arm import arm.material.cycles as cycles import arm.material.mat_state as mat_state import arm.material.make_mesh as make_mesh import arm.material.make_finalize as make_finalize import arm.assets as assets -if "DO_RELOAD_MODULE" in locals(): - import arm +if arm.is_reload(__name__): cycles = arm.reload_module(cycles) mat_state = arm.reload_module(mat_state) make_mesh = arm.reload_module(make_mesh) make_finalize = arm.reload_module(make_finalize) assets = arm.reload_module(assets) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) def make(context_id): diff --git a/blender/arm/material/make_voxel.py b/blender/arm/material/make_voxel.py index 90dad58d..5fd84d1e 100644 --- a/blender/arm/material/make_voxel.py +++ b/blender/arm/material/make_voxel.py @@ -4,12 +4,12 @@ import arm.utils import arm.assets as assets import arm.material.mat_state as mat_state -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): arm.utils = arm.reload_module(arm.utils) assets = arm.reload_module(assets) mat_state = arm.reload_module(mat_state) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) def make(context_id): diff --git a/blender/arm/material/mat_batch.py b/blender/arm/material/mat_batch.py index fca06750..1412a8bc 100644 --- a/blender/arm/material/mat_batch.py +++ b/blender/arm/material/mat_batch.py @@ -1,14 +1,14 @@ +import arm import arm.material.cycles as cycles import arm.material.make_shader as make_shader import arm.material.mat_state as mat_state -if "DO_RELOAD_MODULE" in locals(): - import arm +if arm.is_reload(__name__): cycles = arm.reload_module(cycles) make_shader = arm.reload_module(make_shader) mat_state = arm.reload_module(mat_state) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) # TODO: handle groups # TODO: handle cached shaders diff --git a/blender/arm/material/mat_utils.py b/blender/arm/material/mat_utils.py index 497061d8..7403f5dd 100644 --- a/blender/arm/material/mat_utils.py +++ b/blender/arm/material/mat_utils.py @@ -5,13 +5,13 @@ import arm.make_state as make_state import arm.material.cycles as cycles import arm.log as log -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): arm.utils = arm.reload_module(arm.utils) make_state = arm.reload_module(make_state) cycles = arm.reload_module(cycles) log = arm.reload_module(log) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) add_mesh_contexts = [] diff --git a/blender/arm/material/parser_state.py b/blender/arm/material/parser_state.py index 6bd5a1b2..77b01941 100644 --- a/blender/arm/material/parser_state.py +++ b/blender/arm/material/parser_state.py @@ -3,14 +3,14 @@ from typing import List, Set, Tuple, Union, Optional import bpy +import arm from arm.material.shader import Shader, ShaderContext, vec3str, floatstr -if "DO_RELOAD_MODULE" in locals(): - import arm +if arm.is_reload(__name__): arm.material.shader = arm.reload_module(arm.material.shader) from arm.material.shader import Shader, ShaderContext, vec3str, floatstr else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) class ParserContext(Enum): diff --git a/blender/arm/material/shader.py b/blender/arm/material/shader.py index afddb2f1..ada7534e 100644 --- a/blender/arm/material/shader.py +++ b/blender/arm/material/shader.py @@ -1,9 +1,9 @@ import arm.utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) # Type aliases for type hints to make it easier to see which kind of # shader data type is stored in a string diff --git a/blender/arm/node_utils.py b/blender/arm/node_utils.py index 339aef1c..00c2e4c1 100755 --- a/blender/arm/node_utils.py +++ b/blender/arm/node_utils.py @@ -10,12 +10,12 @@ import arm.log import arm.logicnode.arm_sockets import arm.utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): arm.log = arm.reload_module(arm.log) arm.logicnode.arm_sockets = arm.reload_module(arm.logicnode.arm_sockets) arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) def find_node_by_link(node_group, to_node, inp): diff --git a/blender/arm/nodes_logic.py b/blender/arm/nodes_logic.py index d266f920..d6ac713b 100755 --- a/blender/arm/nodes_logic.py +++ b/blender/arm/nodes_logic.py @@ -11,7 +11,7 @@ import arm.props_traits import arm.ui_icons as ui_icons import arm.utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): arm_nodes = arm.reload_module(arm_nodes) arm.logicnode.replacement = arm.reload_module(arm.logicnode.replacement) arm.logicnode = arm.reload_module(arm.logicnode) @@ -19,7 +19,7 @@ if "DO_RELOAD_MODULE" in locals(): ui_icons = arm.reload_module(ui_icons) arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) registered_nodes = [] registered_categories = [] diff --git a/blender/arm/nodes_material.py b/blender/arm/nodes_material.py index 04bfd8a7..fd6750c6 100644 --- a/blender/arm/nodes_material.py +++ b/blender/arm/nodes_material.py @@ -2,18 +2,18 @@ import bpy import nodeitems_utils from nodeitems_utils import NodeCategory +import arm import arm.material.arm_nodes.arm_nodes as arm_nodes # Import all nodes so that they register. Do not remove this import # even if it looks unused from arm.material.arm_nodes import * -if "DO_RELOAD_MODULE" in locals(): - import arm +if arm.is_reload(__name__): arm_nodes = arm.reload_module(arm_nodes) arm.material.arm_nodes = arm.reload_module(arm.material.arm_nodes) from arm.material.arm_nodes import * else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) registered_nodes = [] diff --git a/blender/arm/profiler.py b/blender/arm/profiler.py index ca96b70f..3c657981 100644 --- a/blender/arm/profiler.py +++ b/blender/arm/profiler.py @@ -2,15 +2,15 @@ import cProfile import os import pstats +import arm import arm.log as log import arm.utils as utils -if "DO_RELOAD_MODULE" in locals(): - import arm +if arm.is_reload(__name__): log = arm.reload_module(log) utils = arm.reload_module(utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) class Profile: diff --git a/blender/arm/props.py b/blender/arm/props.py index 06a1bb99..7d71f07c 100755 --- a/blender/arm/props.py +++ b/blender/arm/props.py @@ -10,7 +10,7 @@ import arm.nodes_logic import arm.proxy import arm.utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): assets = arm.reload_module(assets) arm.logicnode.replacement = arm.reload_module(arm.logicnode.replacement) arm.make = arm.reload_module(arm.make) @@ -18,7 +18,7 @@ if "DO_RELOAD_MODULE" in locals(): arm.proxy = arm.reload_module(arm.proxy) arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) # Armory version arm_version = '2021.8' diff --git a/blender/arm/props_bake.py b/blender/arm/props_bake.py index b096b36d..50af8499 100644 --- a/blender/arm/props_bake.py +++ b/blender/arm/props_bake.py @@ -7,11 +7,11 @@ from arm.lightmapper import operators, properties, utility import arm.assets import arm.utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): arm.assets = arm.reload_module(arm.assets) arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) class ArmBakeListItem(bpy.types.PropertyGroup): diff --git a/blender/arm/props_exporter.py b/blender/arm/props_exporter.py index 775489c2..2cbbbfdb 100644 --- a/blender/arm/props_exporter.py +++ b/blender/arm/props_exporter.py @@ -11,11 +11,11 @@ from bpy.props import * import arm.assets as assets import arm.utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): assets = arm.reload_module(assets) arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) def remove_readonly(func, path, excinfo): diff --git a/blender/arm/props_renderpath.py b/blender/arm/props_renderpath.py index 08aa947a..6196f4f3 100644 --- a/blender/arm/props_renderpath.py +++ b/blender/arm/props_renderpath.py @@ -4,11 +4,11 @@ from bpy.props import * import arm.assets as assets import arm.utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): assets = arm.reload_module(assets) arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) atlas_sizes = [ ('256', '256', '256'), ('512', '512', '512'), diff --git a/blender/arm/props_traits.py b/blender/arm/props_traits.py index 74918ee2..7f873440 100755 --- a/blender/arm/props_traits.py +++ b/blender/arm/props_traits.py @@ -16,7 +16,7 @@ import arm.ui_icons as ui_icons import arm.utils import arm.write_data as write_data -if "DO_RELOAD_MODULE" in locals(): +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 * @@ -25,7 +25,7 @@ if "DO_RELOAD_MODULE" in locals(): arm.utils = arm.reload_module(arm.utils) arm.write_data = arm.reload_module(arm.write_data) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) ICON_HAXE = ui_icons.get_id('haxe') ICON_NODES = 'NODETREE' diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index 3132880d..b7b90276 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -22,7 +22,8 @@ import arm.proxy import arm.ui_icons as ui_icons import arm.utils -if "DO_RELOAD_MODULE" in locals(): + +if arm.is_reload(__name__): arm.api = arm.reload_module(arm.api) assets = arm.reload_module(assets) arm.exporter = arm.reload_module(arm.exporter) @@ -39,7 +40,7 @@ if "DO_RELOAD_MODULE" in locals(): ui_icons = arm.reload_module(ui_icons) arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) class ARM_PT_ObjectPropsPanel(bpy.types.Panel): diff --git a/blender/arm/ui_icons.py b/blender/arm/ui_icons.py index 9a40b4f0..38f6b922 100644 --- a/blender/arm/ui_icons.py +++ b/blender/arm/ui_icons.py @@ -6,7 +6,9 @@ from typing import Optional import bpy.utils.previews -if "DO_RELOAD_MODULE" in locals(): +import arm + +if arm.is_reload(__name__): # _unload_icons is not available in the module scope yet def __unload(): _unload_icons() @@ -14,7 +16,7 @@ if "DO_RELOAD_MODULE" in locals(): # Refresh icons after reload __unload() else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) __all__ = ["get_id"] diff --git a/blender/arm/utils.py b/blender/arm/utils.py index 40a7c6d6..c23100f0 100755 --- a/blender/arm/utils.py +++ b/blender/arm/utils.py @@ -20,7 +20,7 @@ import arm.log as log import arm.make_state as state import arm.props_renderpath -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): arm.lib.armpack = arm.reload_module(arm.lib.armpack) arm.lib.lz4 = arm.reload_module(arm.lib.lz4) from arm.lib.lz4 import LZ4 @@ -28,7 +28,7 @@ if "DO_RELOAD_MODULE" in locals(): state = arm.reload_module(state) arm.props_renderpath = arm.reload_module(arm.props_renderpath) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) class NumpyEncoder(json.JSONEncoder): diff --git a/blender/arm/write_data.py b/blender/arm/write_data.py index 0e0cac42..cace8058 100755 --- a/blender/arm/write_data.py +++ b/blender/arm/write_data.py @@ -11,13 +11,13 @@ import arm.assets as assets import arm.make_state as state import arm.utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): import arm assets = arm.reload_module(assets) state = arm.reload_module(state) arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) def on_same_drive(path1: str, path2: str) -> bool: diff --git a/blender/arm/write_probes.py b/blender/arm/write_probes.py index 83a55a37..857aa88c 100644 --- a/blender/arm/write_probes.py +++ b/blender/arm/write_probes.py @@ -11,13 +11,13 @@ import arm.assets as assets import arm.log as log import arm.utils -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): import arm assets = arm.reload_module(assets) log = arm.reload_module(log) arm.utils = arm.reload_module(arm.utils) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) def add_irr_assets(output_file_irr): diff --git a/blender/start.py b/blender/start.py index 19d75bd2..8f2ff758 100755 --- a/blender/start.py +++ b/blender/start.py @@ -21,7 +21,7 @@ import arm.keymap reload_started = 0 -if "DO_RELOAD_MODULE" in locals(): +if arm.is_reload(__name__): arm.log.debug('Reloading Armory SDK...') reload_started = time.time() @@ -46,7 +46,7 @@ if "DO_RELOAD_MODULE" in locals(): arm.utils = arm.reload_module(arm.utils) arm.keymap = arm.reload_module(arm.keymap) else: - DO_RELOAD_MODULE = True + arm.enable_reload(__name__) registered = False From ca96174b6b83ec8da2d79680cc470d1859e61323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 14 Aug 2021 23:45:33 +0200 Subject: [PATCH 263/264] Fix nishita sky artifacts on some GPUs It could happen that values returned by dot() were slightly larger than 1 or less than -1 due to precision errors, but acos() is undefined outside of [-1, 1]. This would lead to NaN values on some GPUs, causing visible artifacts. --- Shaders/std/math.glsl | 5 +++++ Shaders/std/sky.glsl | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Shaders/std/math.glsl b/Shaders/std/math.glsl index 93293401..279b9898 100755 --- a/Shaders/std/math.glsl +++ b/Shaders/std/math.glsl @@ -40,4 +40,9 @@ float attenuate(const float dist) { // 1.0 / (quadratic * dist * dist); } +float safe_acos(const float x) { + // acos is undefined if |x| > 1 + return acos(clamp(x, -1.0, 1.0)); +} + #endif diff --git a/Shaders/std/sky.glsl b/Shaders/std/sky.glsl index e192bb11..112aaff1 100644 --- a/Shaders/std/sky.glsl +++ b/Shaders/std/sky.glsl @@ -20,6 +20,8 @@ #ifndef _SKY_GLSL_ #define _SKY_GLSL_ +#include "std/math.glsl" + uniform sampler2D nishitaLUT; uniform vec2 nishitaDensity; @@ -119,7 +121,7 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa // Idea behind this: "Rotate" everything by iPos (-> iPos is the new zenith) and then all calculations for the // inner integral only depend on the sample height (iHeight) and sunTheta (angle between sun and new zenith). - float sunTheta = acos(dot(normalize(iPos), normalize(pSun))); + float sunTheta = safe_acos(dot(normalize(iPos), normalize(pSun))); vec3 jAttn = nishita_lookupLUT(iHeight, sunTheta); // Calculate attenuation From 1de97e18985fc2d7d6f7cd079f2dace27e35f5b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Br=C3=BCckner?= Date: Sat, 14 Aug 2021 23:57:28 +0200 Subject: [PATCH 264/264] Minor cleanup --- Shaders/std/math.glsl | 2 +- Shaders/std/sky.glsl | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/Shaders/std/math.glsl b/Shaders/std/math.glsl index 279b9898..a4a64138 100755 --- a/Shaders/std/math.glsl +++ b/Shaders/std/math.glsl @@ -23,7 +23,7 @@ vec2 rand2(const vec2 coord) { const float width = 1100.0; const float height = 500.0; float noiseX = ((fract(1.0 - coord.s * (width / 2.0)) * 0.25) + (fract(coord.t * (height / 2.0)) * 0.75)) * 2.0 - 1.0; - float noiseY = ((fract(1.0 - coord.s * (width / 2.0)) * 0.75) + (fract(coord.t * (height / 2.0)) * 0.25)) * 2.0 - 1.0; + float noiseY = ((fract(1.0 - coord.s * (width / 2.0)) * 0.75) + (fract(coord.t * (height / 2.0)) * 0.25)) * 2.0 - 1.0; return vec2(noiseX, noiseY); } diff --git a/Shaders/std/sky.glsl b/Shaders/std/sky.glsl index 112aaff1..eaac4352 100644 --- a/Shaders/std/sky.glsl +++ b/Shaders/std/sky.glsl @@ -46,15 +46,9 @@ uniform vec2 nishitaDensity; #define nishita_mie_dir 0.76 // Aerosols anisotropy ("direction") #define nishita_mie_dir_sq 0.5776 // Squared aerosols anisotropy - // Values from [Hill: 60] #define sun_limb_darkening_col vec3(0.397, 0.503, 0.652) -float random(vec2 coords) { - // Returned value is in [0, 1] - return fract(sin(dot(coords.xy, vec2(12.9898,78.233))) * 43758.5453); -} - vec3 nishita_lookupLUT(const float height, const float sunTheta) { vec2 coords = vec2( sqrt(height * (1 / nishita_atmo_radius)), @@ -133,7 +127,7 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa vec3 attn = iAttn * jAttn; // Apply dithering to reduce visible banding - attn *= 0.98 + random(r.xy) * 0.04; + attn *= 0.98 + rand(r.xy) * 0.04; // Accumulate scattering totalRlh += odStepRlh * attn;