{lyn2265} adding a Python API for MotionGroup rules (#6215)
* {lyn2265} adding a Python API for MotionGroup rules
Signed-off-by: Allen Jackson <23512001+jackalbe@users.noreply.github.com>
* updating the license headers
added more proper example code for motion group
Signed-off-by: Allen Jackson <23512001+jackalbe@users.noreply.github.com>
* updating the actor group to use the common rules module
Signed-off-by: Allen Jackson <23512001+jackalbe@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4c1d04d687bdce69965965c290c907b3f9a730a7ea73874b734e1c7ff41426d9
|
||||
size 3832768
|
||||
@@ -0,0 +1,66 @@
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
import traceback, sys, uuid, os, json
|
||||
|
||||
import scene_export_utils
|
||||
import scene_api.motion_group
|
||||
|
||||
#
|
||||
# Example for exporting MotionGroup scene rules
|
||||
#
|
||||
|
||||
def update_manifest(scene):
|
||||
import azlmbr.scene.graph
|
||||
import scene_api.scene_data
|
||||
|
||||
# create a SceneManifest
|
||||
sceneManifest = scene_api.scene_data.SceneManifest()
|
||||
|
||||
# create a MotionGroup
|
||||
motionGroup = scene_api.motion_group.MotionGroup()
|
||||
motionGroup.name = os.path.basename(scene.sourceFilename.replace('.', '_'))
|
||||
|
||||
motionAdditiveRule = scene_api.motion_group.MotionAdditiveRule()
|
||||
motionAdditiveRule.sampleFrame = 2
|
||||
motionGroup.add_rule(motionAdditiveRule)
|
||||
|
||||
motionScaleRule = motionGroup.create_rule(scene_api.motion_group.MotionScaleRule())
|
||||
motionScaleRule.scaleFactor = 1.1
|
||||
motionGroup.add_rule(motionScaleRule)
|
||||
|
||||
# add motion group to scene manifest
|
||||
sceneManifest.add_motion_group(motionGroup)
|
||||
|
||||
# Convert the manifest to a JSON string and return it
|
||||
return sceneManifest.export()
|
||||
|
||||
sceneJobHandler = None
|
||||
|
||||
def on_update_manifest(args):
|
||||
try:
|
||||
scene = args[0]
|
||||
return update_manifest(scene)
|
||||
except RuntimeError as err:
|
||||
print (f'ERROR - {err}')
|
||||
scene_export_utils.log_exception_traceback()
|
||||
except:
|
||||
scene_export_utils.log_exception_traceback()
|
||||
|
||||
global sceneJobHandler
|
||||
sceneJobHandler.disconnect()
|
||||
sceneJobHandler = None
|
||||
|
||||
# try to create SceneAPI handler for processing
|
||||
try:
|
||||
import azlmbr.scene
|
||||
|
||||
sceneJobHandler = azlmbr.scene.ScriptBuildingNotificationBusHandler()
|
||||
sceneJobHandler.connect()
|
||||
sceneJobHandler.add_callback('OnUpdateManifest', on_update_manifest)
|
||||
except:
|
||||
sceneJobHandler = None
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"values": [
|
||||
{
|
||||
"$type": "ScriptProcessorRule",
|
||||
"scriptFilename": "Assets/TestAnim/scene_export_motion.py"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
import traceback, sys, uuid, os, json
|
||||
|
||||
#
|
||||
# Utility methods for processing scenes
|
||||
#
|
||||
|
||||
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))
|
||||
|
||||
def get_node_names(sceneGraph, nodeTypeName, testEndPoint = False, validList = None):
|
||||
import azlmbr.scene.graph
|
||||
import scene_api.scene_data
|
||||
|
||||
node = sceneGraph.get_root()
|
||||
nodeList = []
|
||||
children = []
|
||||
paths = []
|
||||
|
||||
while node.IsValid():
|
||||
# store children to process after siblings
|
||||
if sceneGraph.has_node_child(node):
|
||||
children.append(sceneGraph.get_node_child(node))
|
||||
|
||||
nodeName = scene_api.scene_data.SceneGraphName(sceneGraph.get_node_name(node))
|
||||
paths.append(nodeName.get_path())
|
||||
|
||||
include = True
|
||||
|
||||
if (validList is not None):
|
||||
include = False # if a valid list filter provided, assume to not include node name
|
||||
name_parts = nodeName.get_path().split('.')
|
||||
for valid in validList:
|
||||
if (valid in name_parts[-1]):
|
||||
include = True
|
||||
break
|
||||
|
||||
# store any node that has provides specifc data content
|
||||
nodeContent = sceneGraph.get_node_content(node)
|
||||
if include and nodeContent.CastWithTypeName(nodeTypeName):
|
||||
if testEndPoint is not None:
|
||||
include = sceneGraph.is_node_end_point(node) is testEndPoint
|
||||
if include:
|
||||
if (len(nodeName.get_path())):
|
||||
nodeList.append(scene_api.scene_data.SceneGraphName(sceneGraph.get_node_name(node)))
|
||||
|
||||
# advance to next node
|
||||
if sceneGraph.has_node_sibling(node):
|
||||
node = sceneGraph.get_node_sibling(node)
|
||||
elif children:
|
||||
node = children.pop()
|
||||
else:
|
||||
node = azlmbr.scene.graph.NodeIndex()
|
||||
|
||||
return nodeList, paths
|
||||
@@ -1,10 +1,10 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
import traceback, logging, json
|
||||
from typing import Tuple, List
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
import json
|
||||
import uuid
|
||||
import os, sys
|
||||
|
||||
sys.path.append(os.path.dirname(__file__))
|
||||
import physics_data
|
||||
import scene_api.physics_data
|
||||
from scene_api.common_rules import RuleEncoder, BaseRule, SceneNodeSelectionList, CommentRule, CoordinateSystemRule
|
||||
|
||||
class ActorGroup():
|
||||
"""
|
||||
@@ -18,10 +17,10 @@ class ActorGroup():
|
||||
|
||||
Attributes
|
||||
----------
|
||||
name:
|
||||
name:
|
||||
Name for the group. This name will also be used as the name for the generated file.
|
||||
|
||||
selectedRootBone:
|
||||
selectedRootBone:
|
||||
The root bone of the animation that will be exported.
|
||||
|
||||
rules: `list` of actor rules (derived from BaseRule)
|
||||
@@ -86,121 +85,15 @@ class ActorGroup():
|
||||
out['rules'] = ruleList
|
||||
return out
|
||||
|
||||
def to_json(self) -> any:
|
||||
def to_json(self, i = 0) -> any:
|
||||
jsonDOM = self.to_dict()
|
||||
return json.dumps(jsonDOM, cls=RuleEncoder, indent=1)
|
||||
return json.dumps(jsonDOM, cls=RuleEncoder, indent=i)
|
||||
|
||||
class RuleEncoder(json.JSONEncoder):
|
||||
"""
|
||||
A helper class to encode the Python classes with to a Python dictionary
|
||||
|
||||
Methods
|
||||
-------
|
||||
|
||||
encode(obj)
|
||||
Converts contents to a Python dictionary for the JSONEncoder
|
||||
|
||||
"""
|
||||
def encode(self, obj):
|
||||
chunk = obj
|
||||
if isinstance(obj, BaseRule):
|
||||
chunk = obj.to_dict()
|
||||
elif isinstance(obj, dict):
|
||||
chunk = obj
|
||||
elif hasattr(obj, 'to_dict'):
|
||||
chunk = obj.to_dict()
|
||||
else:
|
||||
chunk = obj.__dict__
|
||||
|
||||
return super().encode(chunk)
|
||||
|
||||
class BaseRule():
|
||||
"""
|
||||
Base class of the actor rules to help encode the type name of abstract rules
|
||||
|
||||
Parameters
|
||||
----------
|
||||
typename : str
|
||||
A typename the $type will be in the JSON chunk object
|
||||
|
||||
Attributes
|
||||
----------
|
||||
typename: str
|
||||
The type name of the abstract classes to be serialized
|
||||
|
||||
id: UUID
|
||||
a unique ID for the rule
|
||||
|
||||
Methods
|
||||
-------
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
Adds the '$type' member
|
||||
Adds a random 'id' member
|
||||
"""
|
||||
def __init__(self, typename):
|
||||
self.typename = typename
|
||||
self.id = uuid.uuid4()
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.id.__eq__(other.id)
|
||||
|
||||
def __ne__(self, other):
|
||||
return self.__eq__(other) is False
|
||||
|
||||
def __hash__(self):
|
||||
return self.id.__hash__()
|
||||
|
||||
def to_dict(self):
|
||||
data = self.__dict__
|
||||
data['id'] = f"{{{str(self.id)}}}"
|
||||
# rename 'typename' to '$type'
|
||||
data['$type'] = self.typename
|
||||
data.pop('typename')
|
||||
return data
|
||||
|
||||
class SceneNodeSelectionList(BaseRule):
|
||||
"""
|
||||
Contains a list of node names to include (selectedNodes) and to exclude (unselectedNodes)
|
||||
|
||||
Attributes
|
||||
----------
|
||||
selectedNodes: `list` of str
|
||||
The node names to include for this group rule
|
||||
|
||||
unselectedNodes: `list` of str
|
||||
The node names to exclude for this group rule
|
||||
|
||||
Methods
|
||||
-------
|
||||
convert_selection(self, container, key):
|
||||
this adds its contents to an existing dictionary container at a key position
|
||||
|
||||
select_targets(self, selectedList, allNodesList:list)
|
||||
helper function to include a small list of node names from list of all the node names
|
||||
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('SceneNodeSelectionList')
|
||||
self.selectedNodes = []
|
||||
self.unselectedNodes = []
|
||||
|
||||
def convert_selection(self, container, key):
|
||||
container[key] = self.to_dict()
|
||||
|
||||
def select_targets(self, selectedList, allNodesList:list):
|
||||
self.selectedNodes = selectedList
|
||||
self.unselectedNodes = allNodesList.copy()
|
||||
for node in selectedList:
|
||||
if node in self.unselectedNodes:
|
||||
self.unselectedNodes.remove(node)
|
||||
|
||||
class LodNodeSelectionList(SceneNodeSelectionList):
|
||||
"""
|
||||
Level of Detail node selection list
|
||||
|
||||
|
||||
The selected nodes should be joints with BoneData
|
||||
derived from SceneNodeSelectionList
|
||||
see also LodRule
|
||||
@@ -254,11 +147,11 @@ class PhysicsAnimationConfiguration():
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self):
|
||||
self.hitDetectionConfig = physics_data.CharacterColliderConfiguration()
|
||||
self.ragdollConfig = physics_data.RagdollConfiguration()
|
||||
self.clothConfig = physics_data.CharacterColliderConfiguration()
|
||||
self.simulatedObjectColliderConfig = physics_data.CharacterColliderConfiguration()
|
||||
|
||||
self.hitDetectionConfig = scene_api.physics_data.CharacterColliderConfiguration()
|
||||
self.ragdollConfig = scene_api.physics_data.RagdollConfiguration()
|
||||
self.clothConfig = scene_api.physics_data.CharacterColliderConfiguration()
|
||||
self.simulatedObjectColliderConfig = scene_api.physics_data.CharacterColliderConfiguration()
|
||||
|
||||
def to_dict(self):
|
||||
data = {}
|
||||
data["hitDetectionConfig"] = self.hitDetectionConfig.to_dict()
|
||||
@@ -288,8 +181,8 @@ class EMotionFXPhysicsSetup():
|
||||
self.config = PhysicsAnimationConfiguration()
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"config" : self.config.to_dict()
|
||||
return {
|
||||
"config" : self.config.to_dict()
|
||||
}
|
||||
|
||||
class ActorPhysicsSetupRule(BaseRule):
|
||||
@@ -323,7 +216,7 @@ class ActorPhysicsSetupRule(BaseRule):
|
||||
def __init__(self):
|
||||
super().__init__('ActorPhysicsSetupRule')
|
||||
self.data = EMotionFXPhysicsSetup()
|
||||
|
||||
|
||||
def set_hit_detection_config(self, hitDetectionConfig) -> None:
|
||||
self.data.config.hitDetectionConfig = hitDetectionConfig
|
||||
|
||||
@@ -339,7 +232,7 @@ class ActorPhysicsSetupRule(BaseRule):
|
||||
def to_dict(self):
|
||||
data = super().to_dict()
|
||||
data["data"] = self.data.to_dict()
|
||||
return data
|
||||
return data
|
||||
|
||||
class ActorScaleRule(BaseRule):
|
||||
"""
|
||||
@@ -358,46 +251,7 @@ class ActorScaleRule(BaseRule):
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('ActorScaleRule')
|
||||
self.scaleFactor = 1.0
|
||||
|
||||
class CoordinateSystemRule(BaseRule):
|
||||
"""
|
||||
Modify the target coordinate system, applying a transformation to all data (transforms and vertex data if it exists).
|
||||
|
||||
Attributes
|
||||
----------
|
||||
targetCoordinateSystem: int
|
||||
Change the direction the actor/motion will face by applying a post transformation to the data.
|
||||
|
||||
useAdvancedData: bool
|
||||
If True, use advanced settings
|
||||
|
||||
originNodeName: str
|
||||
Select a Node from the scene as the origin for this export.
|
||||
|
||||
rotation: [float, float, float, float]
|
||||
Sets the orientation offset of the processed mesh in degrees. Rotates (yaw, pitch, roll) the group after translation.
|
||||
|
||||
translation: [float, float, float]
|
||||
Moves the group along the given vector3.
|
||||
|
||||
scale: float
|
||||
Sets the scale offset of the processed mesh.
|
||||
|
||||
Methods
|
||||
-------
|
||||
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('CoordinateSystemRule')
|
||||
self.targetCoordinateSystem = 0
|
||||
self.useAdvancedData = False
|
||||
self.originNodeName = ''
|
||||
self.rotation = [0.0, 0.0, 0.0, 1.0]
|
||||
self.translation = [0.0, 0.0, 0.0]
|
||||
self.scale = 1.0
|
||||
self.scaleFactor = 1.0
|
||||
|
||||
class SkeletonOptimizationRule(BaseRule):
|
||||
"""
|
||||
@@ -436,8 +290,8 @@ class LodRule(BaseRule):
|
||||
Set up the level of detail for the meshes in this group.
|
||||
|
||||
The engine supports 6 total lods.
|
||||
1 for the base model then 5 more lods.
|
||||
The rule only captures lods past level 0 so this is set to 5.
|
||||
1 for the base model then 5 more lods.
|
||||
The rule only captures lods past level 0 so this is set to 5.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
@@ -458,7 +312,7 @@ class LodRule(BaseRule):
|
||||
def __init__(self):
|
||||
super().__init__('{3CB103B3-CEAF-49D7-A9DC-5A31E2DF15E4} LodRule')
|
||||
self.nodeSelectionList = [] # list of LodNodeSelectionList
|
||||
|
||||
|
||||
def add_lod_level(self, lodLevel, selectedNodes=None, unselectedNodes=None) -> LodNodeSelectionList:
|
||||
lodNodeSelection = LodNodeSelectionList()
|
||||
lodNodeSelection.selectedNodes = selectedNodes
|
||||
@@ -466,13 +320,13 @@ class LodRule(BaseRule):
|
||||
lodNodeSelection.lodLevel = lodLevel
|
||||
self.nodeSelectionList.append(lodNodeSelection)
|
||||
return lodNodeSelection
|
||||
|
||||
|
||||
def to_dict(self):
|
||||
data = super().to_dict()
|
||||
selectionListList = data.pop('nodeSelectionList')
|
||||
data['nodeSelectionList'] = []
|
||||
for nodeList in selectionListList:
|
||||
data['nodeSelectionList'].append(nodeList.to_dict())
|
||||
data['nodeSelectionList'].append(nodeList.to_dict())
|
||||
return data
|
||||
|
||||
class MorphTargetRule(BaseRule):
|
||||
@@ -499,21 +353,3 @@ class MorphTargetRule(BaseRule):
|
||||
self.targets.convert_selection(data, 'targets')
|
||||
return data
|
||||
|
||||
class CommentRule(BaseRule):
|
||||
"""
|
||||
Add an optional comment to the asset's properties.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
text: str
|
||||
Text for the comment.
|
||||
|
||||
Methods
|
||||
-------
|
||||
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('CommentRule')
|
||||
self.text = ''
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
import traceback, sys, uuid, os, json, logging
|
||||
|
||||
def log_exception_traceback():
|
||||
"""
|
||||
Outputs an exception stacktrace.
|
||||
"""
|
||||
data = traceback.format_exc()
|
||||
logger = logging.getLogger('python')
|
||||
logger.error(data)
|
||||
|
||||
|
||||
class BaseRule():
|
||||
"""
|
||||
Base class of the actor rules to help encode the type name of abstract rules
|
||||
|
||||
Parameters
|
||||
----------
|
||||
typename : str
|
||||
A typename the $type will be in the JSON chunk object
|
||||
|
||||
Attributes
|
||||
----------
|
||||
typename: str
|
||||
The type name of the abstract classes to be serialized
|
||||
|
||||
id: UUID
|
||||
a unique ID for the rule
|
||||
|
||||
Methods
|
||||
-------
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
Adds the '$type' member
|
||||
Adds a random 'id' member
|
||||
Note: Override this method if a derviced class needs to return a custom dictionary
|
||||
|
||||
"""
|
||||
def __init__(self, typename):
|
||||
self.typename = typename
|
||||
self.id = uuid.uuid4()
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.id.__eq__(other.id)
|
||||
|
||||
def __ne__(self, other):
|
||||
return self.__eq__(other) is False
|
||||
|
||||
def __hash__(self):
|
||||
return self.id.__hash__()
|
||||
|
||||
def to_dict(self):
|
||||
data = vars(self)
|
||||
data['id'] = f"{{{str(self.id)}}}"
|
||||
# rename 'typename' to '$type'
|
||||
data['$type'] = self.typename
|
||||
data.pop('typename')
|
||||
return data
|
||||
|
||||
def convert_rule_to_json(rule:BaseRule, indentValue=0):
|
||||
"""
|
||||
Helper function to convert a BaseRule into a JSON string
|
||||
|
||||
Parameters
|
||||
----------
|
||||
obj : any
|
||||
The object to convert to a JSON string as long as the obj class has an *to_dict* method
|
||||
|
||||
indentValue : int
|
||||
The number of spaces to indent between each JSON block/value
|
||||
"""
|
||||
return json.dumps(rule.to_dict(), indent=indentValue, cls=RuleEncoder)
|
||||
|
||||
class CommentRule(BaseRule):
|
||||
"""
|
||||
Add an optional comment to the asset's properties.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
text: str
|
||||
Text for the comment.
|
||||
|
||||
Methods
|
||||
-------
|
||||
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('CommentRule')
|
||||
self.text = ''
|
||||
|
||||
|
||||
class SceneNodeSelectionList(BaseRule):
|
||||
"""
|
||||
Contains a list of node names to include (selectedNodes) and to exclude (unselectedNodes)
|
||||
|
||||
Attributes
|
||||
----------
|
||||
selectedNodes: `list` of str
|
||||
The node names to include for this group rule
|
||||
|
||||
unselectedNodes: `list` of str
|
||||
The node names to exclude for this group rule
|
||||
|
||||
Methods
|
||||
-------
|
||||
convert_selection(self, container, key):
|
||||
this adds its contents to an existing dictionary container at a key position
|
||||
|
||||
select_targets(self, selectedList, allNodesList:list)
|
||||
helper function to include a small list of node names from list of all the node names
|
||||
|
||||
to_dict()
|
||||
Converts contents to a Python dictionary
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('SceneNodeSelectionList')
|
||||
self.selectedNodes = []
|
||||
self.unselectedNodes = []
|
||||
|
||||
def convert_selection(self, container, key):
|
||||
container[key] = self.to_dict()
|
||||
|
||||
def select_targets(self, selectedList, allNodesList:list):
|
||||
self.selectedNodes = selectedList
|
||||
self.unselectedNodes = allNodesList.copy()
|
||||
for node in selectedList:
|
||||
if node in self.unselectedNodes:
|
||||
self.unselectedNodes.remove(node)
|
||||
|
||||
|
||||
class CoordinateSystemRule(BaseRule):
|
||||
"""
|
||||
Modify the target coordinate system, applying a transformation to all data (transforms and vertex data if it exists).
|
||||
|
||||
Attributes
|
||||
----------
|
||||
targetCoordinateSystem: int
|
||||
Change the direction the actor/motion will face by applying a post transformation to the data.
|
||||
|
||||
useAdvancedData: bool
|
||||
If True, use advanced settings
|
||||
|
||||
originNodeName: str
|
||||
Select a Node from the scene as the origin for this export.
|
||||
|
||||
rotation: [float, float, float, float]
|
||||
Sets the orientation offset of the processed mesh in degrees. Rotates (yaw, pitch, roll) the group after translation.
|
||||
|
||||
translation: [float, float, float]
|
||||
Moves the group along the given vector3.
|
||||
|
||||
scale: float
|
||||
Sets the scale offset of the processed mesh.
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('CoordinateSystemRule')
|
||||
self.targetCoordinateSystem = 0
|
||||
self.useAdvancedData = False
|
||||
self.originNodeName = ''
|
||||
self.rotation = [0.0, 0.0, 0.0, 1.0]
|
||||
self.translation = [0.0, 0.0, 0.0]
|
||||
self.scale = 1.0
|
||||
|
||||
class TypeId():
|
||||
"""
|
||||
Wraps a UUID that represents a AZ::TypeId from O3DE
|
||||
|
||||
Attributes
|
||||
----------
|
||||
valud: uuid.Uuid
|
||||
A unique ID that defaults to AZ::TypeId::CreateNull()
|
||||
"""
|
||||
def __init__(self):
|
||||
self.value = uuid.UUID('{00000000-0000-0000-0000-000000000000}')
|
||||
|
||||
def __str__(self):
|
||||
return f"{{{str(self.value)}}}"
|
||||
|
||||
|
||||
class RuleEncoder(json.JSONEncoder):
|
||||
"""
|
||||
A helper class to encode the Python classes with to a Python dictionary
|
||||
|
||||
Methods
|
||||
-------
|
||||
|
||||
default(obj)
|
||||
Converts a single object to a JSON value that can be stored with a key
|
||||
|
||||
encode(obj)
|
||||
Converts contents to a Python dictionary for the JSONEncoder
|
||||
|
||||
"""
|
||||
def default(self, obj):
|
||||
if (isinstance(obj,TypeId)):
|
||||
return str(obj)
|
||||
elif hasattr(obj, 'to_json_value'):
|
||||
return obj.to_json_value()
|
||||
return super().default(obj)
|
||||
|
||||
def encode(self, obj):
|
||||
chunk = obj
|
||||
if isinstance(obj, BaseRule):
|
||||
chunk = obj.to_dict()
|
||||
elif isinstance(obj, dict):
|
||||
chunk = obj
|
||||
elif hasattr(obj, 'to_dict'):
|
||||
chunk = obj.to_dict()
|
||||
else:
|
||||
chunk = obj.__dict__
|
||||
|
||||
return super().encode(chunk)
|
||||
@@ -0,0 +1,214 @@
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
from enum import Enum
|
||||
import scene_api.common_rules
|
||||
|
||||
class MotionGroup(scene_api.common_rules.BaseRule):
|
||||
"""
|
||||
Configure animation data for exporting.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
name: str
|
||||
Name for the group.
|
||||
This name will also be used as the name for the generated file.
|
||||
|
||||
selectedRootBone: str
|
||||
The root bone of the animation that will be exported.
|
||||
|
||||
rules: list of BaseRule
|
||||
Add or remove rules to fine-tune the export process.
|
||||
List of rules for a motion group including:
|
||||
MotionScaleRule
|
||||
CoordinateSystemRule
|
||||
MotionRangeRule
|
||||
MotionAdditiveRule
|
||||
MotionSamplingRule
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('MotionGroup')
|
||||
self.name = ''
|
||||
self.selectedRootBone = ''
|
||||
self.rules = set()
|
||||
|
||||
def add_rule(self, rule) -> bool:
|
||||
if (rule not in self.rules):
|
||||
self.rules.add(rule)
|
||||
return True
|
||||
return False
|
||||
|
||||
def create_rule(self, rule) -> any:
|
||||
if (self.add_rule(rule)):
|
||||
return rule
|
||||
return None
|
||||
|
||||
def remove_rule(self, type) -> None:
|
||||
self.rules.discard(rule)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
out = super().to_dict()
|
||||
out['name'] = self.name
|
||||
out['selectedRootBone'] = self.selectedRootBone
|
||||
# convert the rules
|
||||
ruleList = []
|
||||
for rule in self.rules:
|
||||
ruleList.append(rule.to_dict())
|
||||
out['rules'] = ruleList
|
||||
return out
|
||||
|
||||
def to_json(self) -> str:
|
||||
jsonDOM = self.to_dict()
|
||||
return json.dumps(jsonDOM, cls=RuleEncoder)
|
||||
|
||||
|
||||
class MotionCompressionSettingsRule(scene_api.common_rules.BaseRule):
|
||||
"""
|
||||
A BaseRule that ses the error tolerance settings while compressing the animation
|
||||
|
||||
Attributes
|
||||
----------
|
||||
maxTranslationError: float
|
||||
Maximum error allowed in translation.
|
||||
Min 0.0, Max 0.1
|
||||
|
||||
maxRotationError: float
|
||||
Maximum error allowed in rotation.
|
||||
Min 0.0, Max 0.1
|
||||
|
||||
maxScaleError: float
|
||||
Maximum error allowed in scale.
|
||||
Min 0.0, Max 0.01
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('MotionCompressionSettingsRule')
|
||||
self.maxTranslationError = 0.0001
|
||||
self.maxRotationError = 0.0001
|
||||
self.maxScaleError = 0.0001
|
||||
|
||||
class MotionScaleRule(scene_api.common_rules.BaseRule):
|
||||
"""
|
||||
A BaseRule that scales the spatial extent of motion
|
||||
|
||||
Attributes
|
||||
----------
|
||||
scaleFactor: float
|
||||
Scale factor; min 0.0001, max 10000.0
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('MotionScaleRule')
|
||||
self.scaleFactor = 1.0
|
||||
|
||||
|
||||
class MotionRangeRule(scene_api.common_rules.BaseRule):
|
||||
"""
|
||||
A BaseRule that defines the range of the motion that will be exported.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
startFrame: float
|
||||
The start frame of the animation that will be exported.
|
||||
|
||||
endFrame: float
|
||||
The end frame of the animation that will be exported.
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('MotionRangeRule')
|
||||
self.startFrame = 0
|
||||
self.endFrame = 0
|
||||
|
||||
|
||||
class MotionAdditiveRule(scene_api.common_rules.BaseRule):
|
||||
"""
|
||||
A BaseRule that makes the motion an additive motion.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
sampleFrame: int
|
||||
The frame number that the motion will be made relative to.
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('MotionAdditiveRule')
|
||||
self.sampleFrame = 0
|
||||
|
||||
|
||||
class SampleRateMethod(Enum):
|
||||
"""
|
||||
A collection of settings related to sampling of the motion
|
||||
|
||||
Attributes
|
||||
----------
|
||||
|
||||
FromSourceScene: int, value = 0
|
||||
Use the source scene's sample rate
|
||||
|
||||
|
||||
Custom: int, value = 1
|
||||
Use the use a custom sample rate
|
||||
"""
|
||||
FromSourceScene = 0
|
||||
Custom = 1
|
||||
|
||||
def to_json_value(self):
|
||||
if(self == SampleRateMethod.FromSourceScene):
|
||||
return 0
|
||||
return 1
|
||||
|
||||
|
||||
class MotionSamplingRule(scene_api.common_rules.BaseRule):
|
||||
"""
|
||||
A collection of settings related to sampling of the motion
|
||||
|
||||
Attributes
|
||||
----------
|
||||
motionDataType: scene_api.common_rules.TypeId()
|
||||
The motion data type to use. This defines how the motion data is stored.
|
||||
This can have an effect on performance and memory usage.
|
||||
|
||||
sampleRateMethod: SampleRateMethod
|
||||
Either use the sample rate from the source scene file or use a custom sample rate.
|
||||
The sample rate is automatically limited to the rate from source scene file (e.g. FBX)
|
||||
|
||||
customSampleRate: float
|
||||
Overwrite the sample rate of the motion, in frames per second.
|
||||
Min: 1.0, Max 240.0
|
||||
|
||||
translationQualityPercentage: float
|
||||
The percentage of quality for translation. Higher values preserve quality, but increase memory usage.
|
||||
Min: 1.0, Max 100.0
|
||||
|
||||
rotationQualityPercentage: float
|
||||
The percentage of quality for rotation. Higher values preserve quality, but increase memory usage.
|
||||
Min: 1.0, Max 100.0
|
||||
|
||||
scaleQualityPercentage: float
|
||||
The percentage of quality for scale. Higher values preserve quality, but increase memory usage.
|
||||
Min: 1.0, Max 100.0
|
||||
|
||||
allowedSizePercentage: float
|
||||
The percentage of extra memory usage allowed compared to the smallest size.
|
||||
For example a value of 10 means we are allowed 10 percent more memory worst case, in trade for extra performance.
|
||||
Allow 15 percent larger size, in trade for performance (in Automatic mode, so when m_motionDataType is a Null typeId).
|
||||
Min: 0.0, Max 100.0
|
||||
|
||||
keepDuration: bool
|
||||
When enabled this keep the duration the same as the Fbx motion duration, even if no joints are animated.
|
||||
When this option is disabled and the motion doesn't animate any joints then the resulting motion will have a duration of zero seconds.
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__('MotionSamplingRule')
|
||||
self.motionDataType = scene_api.common_rules.TypeId()
|
||||
self.sampleRateMethod = SampleRateMethod.FromSourceScene
|
||||
self.customSampleRate = 60.0
|
||||
self.translationQualityPercentage = 75.0
|
||||
self.rotationQualityPercentage = 75.0
|
||||
self.scaleQualityPercentage = 75.0
|
||||
self.allowedSizePercentage = 15.0
|
||||
self.keepDuration = True
|
||||
@@ -1,9 +1,10 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
|
||||
# for underlying data structures, see Code\Framework\AzFramework\AzFramework\Physics\Shape.h
|
||||
|
||||
@@ -69,7 +70,7 @@ class CharacterColliderNodeConfiguration():
|
||||
name : str
|
||||
debug name of the node
|
||||
|
||||
shapes : `list` of `tuple` of (ColliderConfiguration, ShapeConfiguration)
|
||||
shapes : `list` of `tuple` of (ColliderConfiguration, ShapeConfiguration)
|
||||
a list of pairs of collider and shape configuration
|
||||
|
||||
Methods
|
||||
@@ -83,12 +84,12 @@ class CharacterColliderNodeConfiguration():
|
||||
"""
|
||||
def __init__(self):
|
||||
self.name = ''
|
||||
self.shapes = [] # List of Tuple of (ColliderConfiguration, ShapeConfiguration)
|
||||
|
||||
self.shapes = [] # List of Tuple of (ColliderConfiguration, ShapeConfiguration)
|
||||
|
||||
def add_collider_shape_pair(self, colliderConfiguration, shapeConfiguration) -> None:
|
||||
pair = (colliderConfiguration, shapeConfiguration)
|
||||
self.shapes.append(pair)
|
||||
|
||||
|
||||
def to_dict(self):
|
||||
data = {}
|
||||
shapeList = []
|
||||
@@ -115,7 +116,7 @@ class CharacterColliderConfiguration():
|
||||
Helper function to add a character collider node configuration into the nodes
|
||||
|
||||
add_character_collider_node_configuration_node(name, colliderConfiguration, shapeConfiguration)
|
||||
Helper function to add a character collider node configuration into the nodes
|
||||
Helper function to add a character collider node configuration into the nodes
|
||||
|
||||
**Returns**: CharacterColliderNodeConfiguration
|
||||
|
||||
@@ -125,10 +126,10 @@ class CharacterColliderConfiguration():
|
||||
"""
|
||||
def __init__(self):
|
||||
self.nodes = [] # list of CharacterColliderNodeConfiguration
|
||||
|
||||
|
||||
def add_character_collider_node_configuration(self, characterColliderNodeConfiguration) -> None:
|
||||
self.nodes.append(characterColliderNodeConfiguration)
|
||||
|
||||
|
||||
def add_character_collider_node_configuration_node(self, name, colliderConfiguration, shapeConfiguration) -> CharacterColliderNodeConfiguration:
|
||||
characterColliderNodeConfiguration = CharacterColliderNodeConfiguration()
|
||||
self.add_character_collider_node_configuration(characterColliderNodeConfiguration)
|
||||
@@ -169,7 +170,7 @@ class ShapeConfiguration():
|
||||
"$type": self._shapeType,
|
||||
"Scale": self.scale
|
||||
}
|
||||
|
||||
|
||||
class SphereShapeConfiguration(ShapeConfiguration):
|
||||
"""
|
||||
The configuration for a Sphere collider
|
||||
@@ -191,7 +192,7 @@ class SphereShapeConfiguration(ShapeConfiguration):
|
||||
def to_dict(self):
|
||||
data = super().to_dict()
|
||||
data['Radius'] = self.radius
|
||||
return data
|
||||
return data
|
||||
|
||||
class BoxShapeConfiguration(ShapeConfiguration):
|
||||
"""
|
||||
@@ -215,7 +216,7 @@ class BoxShapeConfiguration(ShapeConfiguration):
|
||||
data = super().to_dict()
|
||||
data['Configuration'] = self.dimensions
|
||||
return data
|
||||
|
||||
|
||||
class CapsuleShapeConfiguration(ShapeConfiguration):
|
||||
"""
|
||||
The configuration for a Capsule collider
|
||||
@@ -237,13 +238,13 @@ class CapsuleShapeConfiguration(ShapeConfiguration):
|
||||
super().__init__('CapsuleShapeConfiguration')
|
||||
self.height = 1.00
|
||||
self.radius = 0.25
|
||||
|
||||
|
||||
def to_dict(self):
|
||||
data = super().to_dict()
|
||||
data['Height'] = self.height
|
||||
data['Radius'] = self.radius
|
||||
return data
|
||||
|
||||
return data
|
||||
|
||||
class PhysicsAssetShapeConfiguration(ShapeConfiguration):
|
||||
"""
|
||||
The configuration for a Asset collider using a mesh asset for collision
|
||||
@@ -276,7 +277,7 @@ class PhysicsAssetShapeConfiguration(ShapeConfiguration):
|
||||
self.assetScale = [1.0, 1.0, 1.0]
|
||||
self.useMaterialsFromAsset = True
|
||||
self.subdivisionLevel = 4
|
||||
|
||||
|
||||
def set_asset_reference(self, assetReference: str) -> None:
|
||||
self.asset = { "assetHint": assetReference }
|
||||
|
||||
@@ -286,7 +287,7 @@ class PhysicsAssetShapeConfiguration(ShapeConfiguration):
|
||||
data['AssetScale'] = self.assetScale
|
||||
data['UseMaterialsFromAsset'] = self.useMaterialsFromAsset
|
||||
data['SubdivisionLevel'] = self.subdivisionLevel
|
||||
return data
|
||||
return data
|
||||
|
||||
# for underlying data structures, see Code\Framework\AzFramework\AzFramework\Physics\Configuration\JointConfiguration.h
|
||||
|
||||
@@ -313,7 +314,7 @@ class JointConfiguration():
|
||||
ChildLocalPosition: [float, float, float]
|
||||
Joint position relative to child body.
|
||||
|
||||
StartSimulationEnabled: bool
|
||||
StartSimulationEnabled: bool
|
||||
When active, the joint will be enabled when the simulation begins.
|
||||
|
||||
Methods
|
||||
@@ -372,7 +373,7 @@ class SimulatedBodyConfiguration():
|
||||
"orientation" : self.orientation,
|
||||
"startSimulationEnabled" : self.startSimulationEnabled
|
||||
}
|
||||
|
||||
|
||||
|
||||
# for underlying data structures, see Code\Framework\AzFramework\AzFramework\Physics\Configuration\RigidBodyConfiguration.h
|
||||
|
||||
@@ -394,7 +395,7 @@ class RigidBodyConfiguration(SimulatedBodyConfiguration):
|
||||
Local space offset for the center of mass (COM).
|
||||
|
||||
mass: float
|
||||
The mass of the rigid body in kilograms.
|
||||
The mass of the rigid body in kilograms.
|
||||
A value of 0 is treated as infinite.
|
||||
The trajectory of infinite mass bodies cannot be affected by any collisions or forces other than gravity.
|
||||
|
||||
@@ -423,8 +424,8 @@ class RigidBodyConfiguration(SimulatedBodyConfiguration):
|
||||
When active, the rigid body is not affected by gravity or other forces and is moved by script.
|
||||
|
||||
ccdEnabled: bool
|
||||
When active, the rigid body has continuous collision detection (CCD).
|
||||
Use this to ensure accurate collision detection, particularly for fast moving rigid bodies.
|
||||
When active, the rigid body has continuous collision detection (CCD).
|
||||
Use this to ensure accurate collision detection, particularly for fast moving rigid bodies.
|
||||
CCD must be activated in the global PhysX preferences.
|
||||
|
||||
ccdMinAdvanceCoefficient: float
|
||||
@@ -511,7 +512,7 @@ class RigidBodyConfiguration(SimulatedBodyConfiguration):
|
||||
data["Maximum Angular Velocity"] = self.maxAngularVelocity
|
||||
data["Include All Shapes In Mass"] = self.includeAllShapesInMassCalculation
|
||||
data["CCD Min Advance"] = self.ccdMinAdvanceCoefficient
|
||||
data["CCD Friction"] = self.ccdFrictionEnabled
|
||||
data["CCD Friction"] = self.ccdFrictionEnabled
|
||||
return data
|
||||
|
||||
# for underlying data structures, see Code\Framework\AzFramework\AzFramework\Physics\Ragdoll.h
|
||||
@@ -566,7 +567,7 @@ class RagdollConfiguration():
|
||||
def __init__(self):
|
||||
self.nodes = [] # list of RagdollNodeConfiguration
|
||||
self.colliders = CharacterColliderConfiguration()
|
||||
|
||||
|
||||
def add_ragdoll_node_configuration(self, ragdollNodeConfiguration) -> None:
|
||||
self.nodes.append(ragdollNodeConfiguration)
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
import typing
|
||||
import json
|
||||
import azlmbr.scene as sceneApi
|
||||
from enum import IntEnum
|
||||
|
||||
|
||||
# Wraps the AZ.SceneAPI.Containers.SceneGraph.NodeIndex internal class
|
||||
class SceneGraphNodeIndex:
|
||||
def __init__(self, scene_graph_node_index) -> None:
|
||||
@@ -199,6 +199,16 @@ class SceneManifest:
|
||||
self.manifest['values'].append(prefab_group)
|
||||
return prefab_group
|
||||
|
||||
def add_actor_group(self, group) -> dict:
|
||||
groupDict = group.to_dict()
|
||||
self.manifest['values'].append(groupDict)
|
||||
return groupDict
|
||||
|
||||
def add_motion_group(self, group) -> dict:
|
||||
groupDict = group.to_dict()
|
||||
self.manifest['values'].append(groupDict)
|
||||
return groupDict
|
||||
|
||||
def mesh_group_select_node(self, mesh_group: dict, node_name: str) -> None:
|
||||
"""Adds a node as a selected node.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user