From b7f051fb9aea28ed973eab3fdd9a60f18d2d4581 Mon Sep 17 00:00:00 2001 From: amzn-mike <80125227+amzn-mike@users.noreply.github.com> Date: Tue, 30 Nov 2021 15:44:20 -0600 Subject: [PATCH] Procedural Prefabs - Add mesh modifier support (#5894) * Clean up error output Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com> * Add mesh modifiers to scene_data.py Add example usage of modifiers to scene_mesh_to_prefab.py Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com> * Add type hints, cleanup dict creation, fix variable naming Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com> * Add documentation Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com> * Rename DefaultOrValue to __default_or_value Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com> --- .../Editor/Scripts/scene_mesh_to_prefab.py | 14 +- .../Editor/Scripts/scene_api/scene_data.py | 174 ++++++++++++++++-- 2 files changed, 164 insertions(+), 24 deletions(-) diff --git a/AutomatedTesting/Editor/Scripts/scene_mesh_to_prefab.py b/AutomatedTesting/Editor/Scripts/scene_mesh_to_prefab.py index e832b1f82b..7b9349e270 100644 --- a/AutomatedTesting/Editor/Scripts/scene_mesh_to_prefab.py +++ b/AutomatedTesting/Editor/Scripts/scene_mesh_to_prefab.py @@ -5,7 +5,7 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT # # -import os, traceback, binascii, sys, json, pathlib +import os, traceback, binascii, sys, json, pathlib, logging import azlmbr.math import azlmbr.bus @@ -15,9 +15,9 @@ import azlmbr.bus def log_exception_traceback(): - exc_type, exc_value, exc_tb = sys.exc_info() - data = traceback.format_exception(exc_type, exc_value, exc_tb) - print(str(data)) + data = traceback.format_exc() + logger = logging.getLogger('python') + logger.error(data) def get_mesh_node_names(sceneGraph): import azlmbr.scene as sceneApi @@ -114,12 +114,18 @@ def update_manifest(scene): mesh_group['id'] = '{' + str(uuid.uuid5(uuid.NAMESPACE_DNS, source_filename_only + mesh_path)) + '}' # Set our current node as the only node that is included in this MeshGroup scene_manifest.mesh_group_select_node(mesh_group, mesh_path) + scene_manifest.mesh_group_add_comment(mesh_group, "Hello World") # Explicitly remove all other nodes to prevent implicit inclusions for node in all_node_paths: if node != mesh_path: scene_manifest.mesh_group_unselect_node(mesh_group, node) + scene_manifest.mesh_group_add_cloth_rule(mesh_group, mesh_path, "Col0", 1, "Col0", 2, "Col0", 2, 3) + scene_manifest.mesh_group_add_advanced_mesh_rule(mesh_group, True, False, True, "Col0") + scene_manifest.mesh_group_add_skin_rule(mesh_group, 3, 0.002) + scene_manifest.mesh_group_add_tangent_rule(mesh_group, 1, 0) + # Create an editor entity entity_id = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "CreateEditorReadyEntity", mesh_group_name) # Add an EditorMeshComponent to the entity diff --git a/Gems/PythonAssetBuilder/Editor/Scripts/scene_api/scene_data.py b/Gems/PythonAssetBuilder/Editor/Scripts/scene_api/scene_data.py index 2578efeae2..d105ecdb43 100755 --- a/Gems/PythonAssetBuilder/Editor/Scripts/scene_api/scene_data.py +++ b/Gems/PythonAssetBuilder/Editor/Scripts/scene_api/scene_data.py @@ -95,7 +95,7 @@ class SceneManifest(): def __init__(self): self.manifest = {'values': []} - def add_mesh_group(self, name) -> dict: + def add_mesh_group(self, name: str) -> dict: meshGroup = {} meshGroup['$type'] = '{07B356B7-3635-40B5-878A-FAC4EFD5AD86} MeshGroup' meshGroup['name'] = name @@ -104,7 +104,7 @@ class SceneManifest(): self.manifest['values'].append(meshGroup) return meshGroup - def add_prefab_group(self, name, id, json) -> dict: + def add_prefab_group(self, name: str, id: str, json: dict) -> dict: prefabGroup = {} prefabGroup['$type'] = '{99FE3C6F-5B55-4D8B-8013-2708010EC715} PrefabGroup' prefabGroup['name'] = name @@ -113,30 +113,164 @@ class SceneManifest(): self.manifest['values'].append(prefabGroup) return prefabGroup - def mesh_group_select_node(self, meshGroup, nodeName): - meshGroup['nodeSelectionList']['selectedNodes'].append(nodeName) + def mesh_group_select_node(self, mesh_group: dict, node_name: str) -> None: + mesh_group['nodeSelectionList']['selectedNodes'].append(node_name) - def mesh_group_unselect_node(self, meshGroup, nodeName): - meshGroup['nodeSelectionList']['unselectedNodes'].append(nodeName) + def mesh_group_unselect_node(self, mesh_group: dict, node_name: str) -> None: + mesh_group['nodeSelectionList']['unselectedNodes'].append(node_name) - def mesh_group_add_advanced_coordinate_system(self, meshGroup, originNodeName, translation, rotation, scale): - originRule = {} - originRule['$type'] = 'CoordinateSystemRule' - originRule['useAdvancedData'] = True - originRule['originNodeName'] = '' if originNodeName is None else originNodeName + def mesh_group_add_advanced_coordinate_system(self, mesh_group: dict, origin_node_name: str, translation: object, + rotation: object, scale: float) -> None: + origin_rule = { + '$type': 'CoordinateSystemRule', + 'useAdvancedData': True, + 'originNodeName': '' if origin_node_name is None else origin_node_name + } if translation is not None: - originRule['translation'] = translation + origin_rule['translation'] = translation if rotation is not None: - originRule['rotation'] = rotation + origin_rule['rotation'] = rotation if scale != 1.0: - originRule['scale'] = scale - meshGroup['rules']['rules'].append(originRule) + origin_rule['scale'] = scale + mesh_group['rules']['rules'].append(origin_rule) - def mesh_group_add_comment(self, meshGroup, comment): - commentRule = {} - commentRule['$type'] = 'CommentRule' - commentRule['comment'] = comment - meshGroup['rules']['rules'].append(commentRule) + def mesh_group_add_comment(self, mesh_group: dict, comment: str) -> None: + commentRule = { + '$type': 'CommentRule', + 'comment': comment + } + mesh_group['rules']['rules'].append(commentRule) + + def __default_or_value(self, val, default): + return default if val is None else val + + def mesh_group_add_cloth_rule(self, mesh_group: dict, cloth_node_name: str, + inverse_masses_stream_name: str, inverse_masses_channel: int, + motion_constraints_stream_name: str, motion_constraints_channel: int, + backstop_stream_name: str, backstop_offset_channel: int, + backstop_radius_channel: int) -> None: + """ + Adds a Cloth rule. 0 = Red, 1 = Green, 2 = Blue, 3 = Alpha + :param mesh_group: Mesh Group to add the cloth rule to + :param cloth_node_name: Name of the node that the rule applies to + :param inverse_masses_stream_name: Name of the color stream to use for inverse masses + :param inverse_masses_channel: Color channel (index) for inverse masses + :param motion_constraints_stream_name: Name of the color stream to use for motion constraints + :param motion_constraints_channel: Color channel (index) for motion constraints + :param backstop_stream_name: Name of the color stream to use for backstop + :param backstop_offset_channel: Color channel (index) for backstop offset value + :param backstop_radius_channel: Color chnanel (index) for backstop radius value + """ + cloth_rule = { + '$type': 'ClothRule', + 'meshNodeName': cloth_node_name, + 'inverseMassesStreamName': self.__default_or_value(inverse_masses_stream_name, 'Default: 1.0') + } + + if inverse_masses_channel is not None: + cloth_rule['inverseMassesChannel'] = inverse_masses_channel + cloth_rule['motionConstraintsStreamName'] = self.__default_or_value(motion_constraints_stream_name, 'Default: 1.0') + if motion_constraints_channel is not None: + cloth_rule['motionConstraintsChannel'] = motion_constraints_channel + cloth_rule['backstopStreamName'] = self.__default_or_value(backstop_stream_name, 'None') + if backstop_offset_channel is not None: + cloth_rule['backstopOffsetChannel'] = backstop_offset_channel + if backstop_radius_channel is not None: + cloth_rule['backstopRadiusChannel'] = backstop_radius_channel + mesh_group['rules']['rules'].append(cloth_rule) + + def mesh_group_add_lod_rule(self, mesh_group: dict) -> dict: + """ + Adds an LOD rule + :param mesh_group: Mesh Group to add the rule to + :return: LOD rule + """ + lod_rule = { + '$type': '{6E796AC8-1484-4909-860A-6D3F22A7346F} LodRule', + 'nodeSelectionList': [] + } + + mesh_group['rules']['rules'].append(lod_rule) + return lod_rule + + def lod_rule_add_lod(self, lod_rule: dict) -> dict: + """ + Adds an LOD level to the LOD rule. Nodes are added in order. The first node added represents LOD1, 2nd LOD2, etc + :param lod_rule: LOD rule to add the LOD level to + :return: LOD level + """ + lod = {'selectedNodes': [], 'unselectedNodes': []} + lod_rule['nodeSelectionList'].append(lod) + return lod + + def lod_select_node(self, lod: dict, selected_node: str) -> None: + """ + Adds a node as a selected node + :param lod: LOD level to add the node to + :param selected_node: Path of the node + """ + lod['selectedNodes'].append(selected_node) + + def lod_unselect_node(self, lod: dict, unselected_node: str) -> None: + """ + Adds a node as an unselected node + :param lod: LOD rule to add the node to + :param unselected_node: Path of the node + """ + lod['unselectedNodes'].append(unselected_node) + + def mesh_group_add_advanced_mesh_rule(self, mesh_group: dict, use_32bit_vertices: bool, merge_meshes: bool, + use_custom_normals: bool, + vertex_color_stream: str) -> None: + """ + Adds an Advanced Mesh rule + :param mesh_group: Mesh Group to add the rule to + :param use_32bit_vertices: False = 16bit vertex position precision. True = 32bit vertex position precision + :param merge_meshes: Merge all meshes into a single mesh + :param use_custom_normals: True = use normals from DCC tool. False = average normals + :param vertex_color_stream: Color stream name to use for Vertex Coloring + """ + rule = { + '$type': 'StaticMeshAdvancedRule', + 'use32bitVertices': self.__default_or_value(use_32bit_vertices, False), + 'mergeMeshes': self.__default_or_value(merge_meshes, True), + 'useCustomNormals': self.__default_or_value(use_custom_normals, True) + } + + if vertex_color_stream is not None: + rule['vertexColorStreamName'] = vertex_color_stream + + mesh_group['rules']['rules'].append(rule) + + def mesh_group_add_skin_rule(self, mesh_group: dict, max_weights_per_vertex: int, weight_threshold: float) -> None: + """ + Adds a Skin rule + :param mesh_group: Mesh Group to add the rule to + :param max_weights_per_vertex: Max number of joints that can influence a vertex + :param weight_threshold: Weight values below this value will be treated as 0 + """ + rule = { + '$type': 'SkinRule', + 'maxWeightsPerVertex': self.__default_or_value(max_weights_per_vertex, 4), + 'weightThreshold': self.__default_or_value(weight_threshold, 0.001) + } + + mesh_group['rules']['rules'].append(rule) + + def mesh_group_add_tangent_rule(self, mesh_group: dict, tangent_space: int, tspace_method: int) -> None: + """ + Adds a Tangent rule to control tangent space generation + :param mesh_group: Mesh Group to add the rule to + :param tangent_space: Tangent space source. 0 = Scene, 1 = MikkT Tangent Generation + :param tspace_method: MikkT Generation method. 0 = TSpace, 1 = TSpaceBasic + """ + rule = { + '$type': 'TangentsRule', + 'tangentSpace': self.__default_or_value(tangent_space, 1), + 'tSpaceMethod': self.__default_or_value(tspace_method, 0) + } + + mesh_group['rules']['rules'].append(rule) def export(self): return json.dumps(self.manifest)