6d2765ef42
from 'project root' to 'project root/Assets/Physics'
The functionality of creating / using the default physmaterial file has only change in related to the file location, other functionality is unchanged.
The following situations can occur:
This will not affect have any project that uses a custom physmaterial file.
This will not affect have any project that uses the default from the old location, as the configuration will still point there.
New projects created will get the default physmaterial file at the new location.
A Project that fails to load (or deletes) the selected physmaterial file, will get the default physmaterial file at the new location (this happens only on startup of the editor).
Issue: #2765
Signed-off-by: amzn-sean 75276488+amzn-sean@users.noreply.github.com
196 lines
8.3 KiB
Python
Executable File
196 lines
8.3 KiB
Python
Executable File
"""
|
|
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 os
|
|
import azlmbr.legacy.general as general
|
|
|
|
from xml.etree import ElementTree
|
|
|
|
class Physmaterial_Editor:
|
|
"""
|
|
This class is used to adjust physmaterial files for use with Open 3D Engine.
|
|
|
|
NOTEWORTHY:
|
|
- Must use save_changes() for library modifications to take affect
|
|
- Once file is overwritten there is a small lag before the editor applies these changes. Tests
|
|
must be set up to allow time for this lag.
|
|
- You can use parse() to overwrite the Physmaterial_Editor object with a new file
|
|
|
|
Methods:
|
|
- __init__ (self, document_filename = None): Sets up Physmaterial Instance
|
|
- document_filename (type: string): the full path of your physmaterial file
|
|
- parse_file (self): Loads the material library into memory and creates and indexable root object.
|
|
- save_changes (self): Overwrites the contents of the input file with the modified library. Unless
|
|
this is called no changes will occur
|
|
- modify_material (self, material, attribute, value): Modifies a given material. Adjusts values
|
|
if possible, throws errors if not
|
|
- material (type: string): The name of the material, must be exact
|
|
- attribute (type: string): Name of the attribute, must be exact. Restrictions outlined below
|
|
- value (type: string, int, or float): New value for the given attribute. Restrictions
|
|
outlined below
|
|
- delete_material (self, material): Deletes given material from the library.
|
|
- material (type: string): The name of the material, must be exact
|
|
|
|
Properties:
|
|
- number_of_materials: Number of materials in the material library
|
|
|
|
Input Restrictions:
|
|
- Attribute: Can only be one of the five following values
|
|
- 'Dynamic Friction'
|
|
- 'Static Friction'
|
|
- 'Restitution'
|
|
- 'Friction Combine'
|
|
- 'Restitution Combine'
|
|
- Friction Values: Must be a number either int or float
|
|
- Restitution Values: Must be a number either int or float between 0 and 1
|
|
- Combine Values: Can only be one of the four following values
|
|
- 'Average'
|
|
- 'Minimum'
|
|
- 'Maximum'
|
|
- 'Multiply'
|
|
|
|
notes:
|
|
- Due to the setup of material libraries root has a lot of indices that must be used to get to the
|
|
actual library portion. There does not seem to be an easy way to remedy this issue as it makes
|
|
for a difficult rewrite process
|
|
- parse_file must only be called if the file path is not given during initialization.
|
|
"""
|
|
|
|
def __init__(self, document=None):
|
|
self.document_filename = document
|
|
self.project_folder = general.get_game_folder()
|
|
self._set_path()
|
|
self.parse_file()
|
|
|
|
def parse_file(self):
|
|
# type: (str) -> None
|
|
# See if a file exists at the given path
|
|
if not os.path.exists(self.document_filename):
|
|
raise ValueError("Given file, {} ,does not exist".format(self.document_filename))
|
|
# Brings Material Library contents into memory
|
|
try:
|
|
self.dom = ElementTree.parse(self.document_filename)
|
|
except Exception as e:
|
|
print(e)
|
|
raise ValueError('{} not valid'.format(self.document_filename))
|
|
# Turn parsed xml into usable form
|
|
self.root = self.dom.getroot()
|
|
# Check if file is a material library
|
|
asset_typename = self.root[0].get('name')
|
|
if not asset_typename == "MaterialLibraryAsset":
|
|
if asset_typename:
|
|
print("Given file is a {} file".format(self.root[0].get('name')))
|
|
raise ValueError('File not valid')
|
|
|
|
def save_changes(self):
|
|
# type: (None) -> None
|
|
# Over writes file with modified material library contents
|
|
content = ElementTree.tostring(self.root)
|
|
try:
|
|
with open(self.document_filename, "wb") as document:
|
|
document.write(content)
|
|
except Exception as e:
|
|
print(e)
|
|
print("Failed to save changes to script")
|
|
|
|
# Temporary fix, will need to use OnAssetReloaded callbacks
|
|
general.idle_wait(0.5)
|
|
|
|
def delete_material(self, material):
|
|
# type: (str) -> bool
|
|
# Deletes a material from the library
|
|
index = self._find_material_index(material)
|
|
if index != None:
|
|
self.root[0][1].remove(self.root[0][1][index])
|
|
return True
|
|
else:
|
|
print("{} not found in library. No deletion occurred.".format(material))
|
|
return False
|
|
|
|
def modify_material(self, material, attribute, value):
|
|
# type: (str, str, float) -> bool
|
|
# Modifies attributes of a given material in the library
|
|
index = self._find_material_index(material)
|
|
attribute_index = Physmaterial_Editor._get_attribute_index(attribute)
|
|
formated_value = Physmaterial_Editor._value_formater(value, 'Restitution' == attribute, 'Combine' in attribute)
|
|
if index != None:
|
|
self.root[0][1][index][0][attribute_index].set('value', formated_value)
|
|
return True
|
|
else:
|
|
print("{} not found in library. No modification of {} occurred.".format(material, attribute))
|
|
return False
|
|
|
|
@property
|
|
def number_of_materials(self):
|
|
# type: (str) -> int
|
|
materials = self.root[0][1].findall(".//Class[@name='MaterialFromAssetConfiguration']")
|
|
return len(materials)
|
|
|
|
def _set_path(self):
|
|
# type: (str) -> str
|
|
if self.document_filename == None:
|
|
self.document_filename = os.path.join(self.project_folder, "assets", "physics", "surfacetypemateriallibrary.physmaterial")
|
|
else:
|
|
for (root, directories, root_files) in os.walk(self.project_folder):
|
|
for root_file in root_files:
|
|
if root_file == self.document_filename:
|
|
self.document_filename = os.path.join(root, root_file)
|
|
break
|
|
|
|
def _find_material_index(self, material):
|
|
# type: (str) -> int
|
|
found = False
|
|
material_index = None
|
|
for index, child in enumerate(self.root[0][1]):
|
|
if child.findall(".//Class[@value='{}']".format(material)):
|
|
if not found:
|
|
found = True
|
|
material_index = index
|
|
return material_index
|
|
|
|
@staticmethod
|
|
def _value_formater(value, is_restitution, is_combine):
|
|
# type: (float/int/str, bool, bool) -> str
|
|
# Constants
|
|
MIN_RESTITUTION = 0.0000000
|
|
MAX_RESTITUTION = 1.0000000
|
|
|
|
if is_combine:
|
|
value = Physmaterial_Editor._get_combine_id(value)
|
|
else:
|
|
if isinstance(value, int) or isinstance(value, float):
|
|
if is_restitution:
|
|
value = max(min(value, MAX_RESTITUTION), MIN_RESTITUTION)
|
|
value = "{:.7f}".format(value)
|
|
else:
|
|
raise ValueError("Must enter int or float. Entered value was of type {}.".format(type(value)))
|
|
return value
|
|
|
|
@staticmethod
|
|
def _get_combine_id(combine_name):
|
|
# type: (str) -> int
|
|
# Maps the Combine mode to its enumerated value used by the Open 3D Engine Editor
|
|
combine_dictionary = {"Average": "0", "Minimum": "1", "Maximum": "2", "Multiply": "3"}
|
|
if combine_name not in combine_dictionary:
|
|
raise ValueError("Invalid Combine Value given. {} is not in combine map".format(combine_name))
|
|
return combine_dictionary[combine_name]
|
|
|
|
@staticmethod
|
|
def _get_attribute_index(attribute):
|
|
# type: (str) -> int
|
|
# Maps the attribute names to their corresponding index relative to the line defining the material name.
|
|
attribute_dictionary = {
|
|
"DynamicFriction": 1,
|
|
"StaticFriction": 2,
|
|
"Restitution": 3,
|
|
"FrictionCombine": 4,
|
|
"RestitutionCombine": 5,
|
|
}
|
|
if attribute not in attribute_dictionary:
|
|
raise ValueError("Invalid Material Attribute given. {} is not in attribute map".format(attribute))
|
|
return attribute_dictionary[attribute]
|