ATOM-15352 Find a solution to modify a render pipeline when enable a feature gem (#960)
* Added atom_rpi_tools python module in Atom_RPI gem. The tool includes functions to modify pass template data and some utility functions. * Added pytest tests for atom_rpi_tools
This commit is contained in:
@@ -10,3 +10,5 @@
|
||||
#
|
||||
|
||||
add_subdirectory(Code)
|
||||
add_subdirectory(Tools)
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
if (PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
ly_pip_install_local_package_editable(${CMAKE_CURRENT_LIST_DIR} atom_rpi_tools)
|
||||
|
||||
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
|
||||
ly_add_pytest(
|
||||
NAME RPI::atom_rpi_tools_tests
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/atom_rpi_tools/tests/
|
||||
TIMEOUT 30
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
its licensors.
|
||||
|
||||
For complete copyright and license terms please see the LICENSE at the root of this
|
||||
distribution (the "License"). All use of this software is governed by the License,
|
||||
or, if provided, by the license below or the license accompanying this file. Do not
|
||||
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
|
||||
INTRODUCTION
|
||||
------------
|
||||
|
||||
atom_rpi_tools is a Python project that contains a collection of tools
|
||||
developed by the Atom team. The project contains the following tools:
|
||||
|
||||
* Render pipeline merge tool:
|
||||
A library to manipulate .pass asset files and help gems create scripts to update render pipeline
|
||||
|
||||
|
||||
REQUIREMENTS
|
||||
------------
|
||||
|
||||
* Python 3.7.5 (64-bit)
|
||||
|
||||
It is recommended that you completely remove any other versions of Python
|
||||
installed on your system.
|
||||
|
||||
|
||||
INSTALL
|
||||
-----------
|
||||
It is recommended to set up these these tools with Lumberyard's CMake build commands.
|
||||
|
||||
|
||||
UNINSTALLATION
|
||||
--------------
|
||||
|
||||
The preferred way to uninstall the project is:
|
||||
(engine install root)/python/python -m pip uninstall atom_rpi_tools
|
||||
@@ -0,0 +1,10 @@
|
||||
"""
|
||||
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
its licensors.
|
||||
|
||||
For complete copyright and license terms please see the LICENSE at the root of this
|
||||
distribution (the "License"). All use of this software is governed by the License,
|
||||
or, if provided, by the license below or the license accompanying this file. Do not
|
||||
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
"""
|
||||
@@ -0,0 +1,210 @@
|
||||
"""
|
||||
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
its licensors.
|
||||
|
||||
For complete copyright and license terms please see the LICENSE at the root of this
|
||||
distribution (the "License"). All use of this software is governed by the License,
|
||||
or, if provided, by the license below or the license accompanying this file. Do not
|
||||
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
"""
|
||||
|
||||
import sys, os
|
||||
import json
|
||||
import shutil
|
||||
|
||||
class PassTemplate:
|
||||
# This class provide necessary functions for insert pass requests and update connections
|
||||
# which are common functions required for adding features.
|
||||
# It doesn't include the remove/delete furnctions since that's not common case for merging render pipeline
|
||||
def __init__(self, filePath: str):
|
||||
self.initialized = False
|
||||
self.file_path: str = filePath
|
||||
#load the json file
|
||||
json_data = open(filePath, "r")
|
||||
self.file_data = json.load(json_data)
|
||||
|
||||
if 'ClassName' not in self.file_data or 'ClassData' not in self.file_data or self.file_data['ClassName']!='PassAsset' or 'PassTemplate' not in self.file_data['ClassData']:
|
||||
raise KeyError('the json file is not a PassAsset file')
|
||||
return
|
||||
|
||||
if 'PassRequests' in self.file_data['ClassData']['PassTemplate']:
|
||||
self.passRequests = self.file_data['ClassData']['PassTemplate']['PassRequests']
|
||||
|
||||
if 'Slots' in self.file_data['ClassData']['PassTemplate']:
|
||||
self.slots = self.file_data['ClassData']['PassTemplate']['Slots']
|
||||
|
||||
self.initialized = True
|
||||
print('PassTemplate is loaded from ', filePath)
|
||||
|
||||
def find_pass(self, passName):
|
||||
# return pass's index in PassRequests if a PassRequest with input passName exists
|
||||
if not hasattr(self, 'passRequests'):
|
||||
return -1
|
||||
index = 0
|
||||
for passRequest in self.passRequests:
|
||||
if passRequest['Name'] == passName:
|
||||
return index
|
||||
index += 1
|
||||
return -1
|
||||
|
||||
def get_pass_count(self):
|
||||
if not hasattr(self, 'passRequests'):
|
||||
return 0
|
||||
return len(self.passRequests)
|
||||
|
||||
def __validate_pass_request_data(self, passRequest):
|
||||
if ('Name' not in passRequest or 'TemplateName' not in passRequest):
|
||||
raise KeyError('invalid pass request data')
|
||||
|
||||
def __ensure_pass_requests_key(self):
|
||||
if not hasattr(self, 'passRequests'):
|
||||
self.file_data['ClassData']['PassTemplate']['PassRequests'] = []
|
||||
self.passRequests = self.file_data['ClassData']['PassTemplate']['PassRequests']
|
||||
|
||||
def __ensure_pass_slots_key(self):
|
||||
if not hasattr(self, 'slots'):
|
||||
self.file_data['ClassData']['PassTemplate']['Slots'] = []
|
||||
self.slots = self.file_data['ClassData']['PassTemplate']['Slots']
|
||||
|
||||
def insert_pass_request(self, location, passRequest):
|
||||
self.__validate_pass_request_data(passRequest)
|
||||
|
||||
if (self.find_pass(passRequest['Name']) >= 0):
|
||||
raise ValueError('pass request ', passRequest['Name'], ' is already exist')
|
||||
# insert a passRequest before the specified location
|
||||
self.__ensure_pass_requests_key()
|
||||
self.passRequests.insert(location, passRequest)
|
||||
|
||||
def replace_references_after(self, startPassRequest, oldPass, oldSlot, newPass, newSlot):
|
||||
if not hasattr(self, 'passRequests'):
|
||||
return 0
|
||||
# from all pass requests after startPassRequest
|
||||
# replace all attachment references which uses oldPass and oldSlot
|
||||
# with newPass and newSlot
|
||||
started = False
|
||||
replaced_count = 0
|
||||
for request in self.passRequests:
|
||||
if started:
|
||||
if ('Connections' in request):
|
||||
for connection in request['Connections']:
|
||||
if connection['AttachmentRef']['Pass'] == oldPass and connection['AttachmentRef']['Attachment'] == oldSlot:
|
||||
connection['AttachmentRef']['Pass'] = newPass
|
||||
connection['AttachmentRef']['Attachment'] = newSlot
|
||||
replaced_count += 1
|
||||
if request['Name'] == startPassRequest and not started:
|
||||
started = True
|
||||
return replaced_count
|
||||
|
||||
def replace_references_for(self, passRequest, oldPass, oldSlot, newPass, newSlot):
|
||||
if not hasattr(self, 'passRequests'):
|
||||
return 0
|
||||
#replace pass reference for the specified passRequest
|
||||
replaced_count = 0
|
||||
for request in self.passRequests:
|
||||
if request['Name'] == passRequest:
|
||||
if ('Connections' in request):
|
||||
for connection in request['Connections']:
|
||||
if connection['AttachmentRef']['Pass'] == oldPass and connection['AttachmentRef']['Attachment'] == oldSlot:
|
||||
connection['AttachmentRef']['Pass'] = newPass
|
||||
connection['AttachmentRef']['Attachment'] = newSlot
|
||||
replaced_count += 1
|
||||
return replaced_count #return when the specified pass request is updated.
|
||||
return replaced_count
|
||||
|
||||
def __validate_slot_data(self, slotData):
|
||||
if ('Name' not in slotData or 'SlotType' not in slotData):
|
||||
raise KeyError('invalid slot data')
|
||||
|
||||
def get_slot_count(self):
|
||||
if not hasattr(self, 'slots'):
|
||||
return 0
|
||||
return len(self.slots)
|
||||
|
||||
def find_slot(self, slotName):
|
||||
# return slot's index in Slots if a PassRequest with input passName exists
|
||||
if not hasattr(self, 'slots'):
|
||||
return -1
|
||||
index = 0
|
||||
for slot in self.slots:
|
||||
if slot['Name'] == slotName:
|
||||
return index
|
||||
index += 1
|
||||
return -1
|
||||
|
||||
def insert_slot(self, location, newSlotData):
|
||||
# insert a new slot at specified location
|
||||
self.__validate_slot_data(newSlotData)
|
||||
# check if the slot already exist
|
||||
if (self.find_slot(newSlotData['Name']) >= 0):
|
||||
raise ValueError('Slot ', newSlotData['Name'], ' is already exist')
|
||||
|
||||
self.__ensure_pass_slots_key()
|
||||
self.slots.insert(location, newSlotData)
|
||||
|
||||
def add_slot(self, newSlotData):
|
||||
# append a new slot to slots
|
||||
self.__validate_slot_data(newSlotData)
|
||||
# check if the slot already exist
|
||||
if (self.find_slot(newSlotData['Name']) >= 0):
|
||||
raise ValueError('Slot ', newSlotData['Name'], ' is already exist')
|
||||
|
||||
self.__ensure_pass_slots_key()
|
||||
self.slots.append(newSlotData)
|
||||
|
||||
def get_pass_request(self, passName):
|
||||
if not hasattr(self, 'passRequests'):
|
||||
return
|
||||
# Get the pass request from PassRequests with matching pass name
|
||||
for passRequest in self.passRequests:
|
||||
if passRequest['Name'] == passName:
|
||||
return passRequest
|
||||
|
||||
def save(self):
|
||||
# backup the original file
|
||||
backupFilePath = self.file_path +'.backup'
|
||||
shutil.copyfile(self.file_path, backupFilePath)
|
||||
# save and overwrite file
|
||||
with open(self.file_path, 'w') as json_file:
|
||||
json.dump(self.file_data, json_file, indent = 4)
|
||||
print('File [', self.file_path, '] is updated. Old version is saved in [', backupFilePath, ']')
|
||||
|
||||
|
||||
class PassRequest:
|
||||
|
||||
def __init__(self, passRequest: object):
|
||||
self.pass_request = passRequest
|
||||
if 'Connections' in passRequest:
|
||||
self.connections = passRequest['Connections']
|
||||
|
||||
def __validate_connection(self, connection):
|
||||
if ('LocalSlot' not in connection or 'AttachmentRef' not in connection):
|
||||
raise KeyError('invalid connection data')
|
||||
|
||||
def __ensure_connections_key(self):
|
||||
if not hasattr(self, 'connections'):
|
||||
self.pass_request['Connections'] = []
|
||||
self.connections = self.pass_request['Connections']
|
||||
|
||||
def get_connection_count(self):
|
||||
if not hasattr(self, 'connections'):
|
||||
return 0
|
||||
return len(self.connections)
|
||||
|
||||
def find_connection(self, localSlotName):
|
||||
if not hasattr(self, 'connections'):
|
||||
return -1
|
||||
index = 0
|
||||
for connection in self.connections:
|
||||
if connection['LocalSlot'] == localSlotName:
|
||||
return index
|
||||
index += 1
|
||||
return -1
|
||||
|
||||
def add_connection(self, newConnection):
|
||||
self.__validate_connection(newConnection)
|
||||
if self.find_connection(newConnection['LocalSlot']) >= 0:
|
||||
raise ValueError('connection ', newConnection['LocalSlot'], ' already exists')
|
||||
self.__ensure_connections_key()
|
||||
self.connections.append(newConnection)
|
||||
@@ -0,0 +1,10 @@
|
||||
"""
|
||||
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
its licensors.
|
||||
|
||||
For complete copyright and license terms please see the LICENSE at the root of this
|
||||
distribution (the "License"). All use of this software is governed by the License,
|
||||
or, if provided, by the license below or the license accompanying this file. Do not
|
||||
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
"""
|
||||
@@ -0,0 +1,303 @@
|
||||
"""
|
||||
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
its licensors.
|
||||
|
||||
For complete copyright and license terms please see the LICENSE at the root of this
|
||||
distribution (the "License"). All use of this software is governed by the License,
|
||||
or, if provided, by the license below or the license accompanying this file. Do not
|
||||
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
Unit tests for pass_data.py
|
||||
"""
|
||||
import os
|
||||
import pytest
|
||||
import shutil
|
||||
import json
|
||||
from atom_rpi_tools.pass_data import PassTemplate
|
||||
from atom_rpi_tools.pass_data import PassRequest
|
||||
|
||||
good_pass_requests_file = os.path.join(os.path.dirname(__file__), 'testdata/pass_requests.json')
|
||||
good_pass_slots_file = os.path.join(os.path.dirname(__file__), 'testdata/pass_slots.json')
|
||||
bad_test_data_file = os.path.join(os.path.dirname(__file__), 'testdata/pass_test_bad.json')
|
||||
|
||||
@pytest.fixture
|
||||
def pass_requests_template(tmpdir):
|
||||
filename = 'pass_requests.json'
|
||||
source_path = os.path.join(os.path.dirname(__file__), 'testdata/', filename)
|
||||
destFilePath = os.path.join(tmpdir, 'pass_requests.json')
|
||||
shutil.copyfile(source_path, destFilePath)
|
||||
return PassTemplate(destFilePath)
|
||||
|
||||
@pytest.fixture
|
||||
def pass_slots_template(tmpdir):
|
||||
filename = 'pass_slots.json'
|
||||
source_path = os.path.join(os.path.dirname(__file__), 'testdata/', filename)
|
||||
destFilePath = os.path.join(tmpdir, 'pass_requests.json')
|
||||
shutil.copyfile(source_path, destFilePath)
|
||||
return PassTemplate(destFilePath)
|
||||
|
||||
@pytest.fixture
|
||||
def new_pass_request():
|
||||
pass_request = json.loads('{\"Name\": \"InsertPass\",\"TemplateName\": \"InsertPassTemplate\"}')
|
||||
return pass_request
|
||||
|
||||
@pytest.fixture
|
||||
def new_slot():
|
||||
slot = json.loads('{\"Name\": \"NewSlot\",\"SlotType\": \"Input\"}')
|
||||
return slot
|
||||
|
||||
@pytest.fixture
|
||||
def new_connection():
|
||||
connection = json.loads('{\"LocalSlot\": \"color\", \"AttachmentRef\": { \"Pass\": \"Parent\", \"Attachment\": \"DepthStencil\"}}')
|
||||
return connection
|
||||
|
||||
def test_PassTemplate_Initialize_BadPassTemplateData_ExceptionThrown():
|
||||
with pytest.raises(KeyError):
|
||||
PassTemplate(bad_test_data_file)
|
||||
|
||||
def test_PassTemplate_FindPass_Success(pass_requests_template):
|
||||
assert pass_requests_template.find_pass('OpaquePass') == 0
|
||||
assert pass_requests_template.find_pass('ImGuiPass') == 4
|
||||
assert pass_requests_template.find_pass('NotExistPass') == -1
|
||||
|
||||
def test_PassTemplate_InsertPassRequest_AtBegining_Success(pass_requests_template, new_pass_request):
|
||||
template = pass_requests_template
|
||||
pass_count = template.get_pass_count()
|
||||
template.insert_pass_request(0, new_pass_request)
|
||||
assert template.find_pass(new_pass_request['Name']) == 0
|
||||
assert template.get_pass_count() == pass_count+1
|
||||
|
||||
# verify the change is saved
|
||||
template.save()
|
||||
saved_tamplate = PassTemplate(template.file_path)
|
||||
assert saved_tamplate.find_pass(new_pass_request['Name'])== 0
|
||||
assert saved_tamplate.get_pass_count() == pass_count+1
|
||||
|
||||
def test_PassTemplate_InsertPassRequest_AtEnd_Success(pass_requests_template, new_pass_request):
|
||||
template = pass_requests_template
|
||||
pass_count = template.get_pass_count()
|
||||
template.insert_pass_request(pass_count, new_pass_request)
|
||||
assert template.find_pass(new_pass_request['Name']) == pass_count
|
||||
assert template.get_pass_count() == pass_count+1
|
||||
|
||||
# verify the change is saved
|
||||
template.save()
|
||||
saved_tamplate = PassTemplate(template.file_path)
|
||||
assert saved_tamplate.find_pass(new_pass_request['Name']) == pass_count
|
||||
assert saved_tamplate.get_pass_count() == pass_count+1
|
||||
|
||||
def test_PassTemplate_InsertPassRequest_WithDuplicatedName_ExceptionThrown(pass_requests_template, new_pass_request):
|
||||
template = pass_requests_template
|
||||
# insert new pass request
|
||||
template.insert_pass_request(0, new_pass_request)
|
||||
pass_count = template.get_pass_count()
|
||||
# exception when insert the same pass again
|
||||
with pytest.raises(ValueError):
|
||||
template.insert_pass_request(2, new_pass_request)
|
||||
# pass count doesn't change
|
||||
assert template.get_pass_count() == pass_count
|
||||
|
||||
def test_PassTemplate_InsertPassRequest_WithBadData_ExceptionThrown(pass_requests_template):
|
||||
template = pass_requests_template
|
||||
pass_count = template.get_pass_count()
|
||||
bad_pass_request = json.loads('{\"name\":\"value\"}')
|
||||
with pytest.raises(KeyError):
|
||||
template.insert_pass_request(2, bad_pass_request)
|
||||
assert template.get_pass_count() == pass_count
|
||||
|
||||
def test_PassTemplate_InsertPassRequest_AtOutOfRange_AppendSuccess(pass_requests_template, new_pass_request):
|
||||
template = pass_requests_template
|
||||
pass_count = template.get_pass_count()
|
||||
template.insert_pass_request(pass_count+2, new_pass_request)
|
||||
assert template.find_pass(new_pass_request['Name']) == pass_count
|
||||
assert template.get_pass_count() == pass_count+1
|
||||
|
||||
def test_PassTemplate_ReplaceReferencesAfter_Success(pass_requests_template):
|
||||
# replace OpaquePass.DepthStencil with Parent.DepthStencil'
|
||||
refPass = 'OpaquePass'
|
||||
# there are 2 passes after OpaquePass which use OpaquePass.DepthStencil as attachment reference
|
||||
assert pass_requests_template.replace_references_after(refPass, 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 2
|
||||
# after the previous replacement, there it no OpaquePass.DepthStencil reference
|
||||
refPass = 'TransparentPass'
|
||||
assert pass_requests_template.replace_references_after(refPass, 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 0
|
||||
|
||||
# verify changes are saved
|
||||
pass_requests_template.save()
|
||||
saved_tamplate = PassTemplate(pass_requests_template.file_path)
|
||||
assert saved_tamplate.replace_references_after('OpaquePass', 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 0
|
||||
|
||||
def test_PassTemplate_ReplaceReferencesFor_Success(pass_requests_template):
|
||||
refPass = 'TransparentPass'
|
||||
assert pass_requests_template.replace_references_for(refPass, 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 1
|
||||
refPass = '2DPass'
|
||||
assert pass_requests_template.replace_references_for(refPass, 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 0
|
||||
|
||||
# verify changes are saved
|
||||
pass_requests_template.save()
|
||||
saved_tamplate = PassTemplate(pass_requests_template.file_path)
|
||||
# no reference of OpaquePass.DepthStencil in TransparentPass
|
||||
assert saved_tamplate.replace_references_for('TransparentPass', 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 0
|
||||
|
||||
def test_PassTemplate_FindSlot_Success(pass_slots_template):
|
||||
assert pass_slots_template.find_slot('Color') == -1
|
||||
assert pass_slots_template.find_slot('DepthStencil') == 0
|
||||
assert pass_slots_template.find_slot('ColorInputOutput') == 1
|
||||
|
||||
def test_PassTemplate_InsertSlot_AtBegining_Success(pass_slots_template, new_slot):
|
||||
template = pass_slots_template
|
||||
slot_count = template.get_slot_count()
|
||||
depth_stencil_slot = template.find_slot('DepthStencil')
|
||||
template.insert_slot(0, new_slot)
|
||||
assert template.find_slot(new_slot['Name']) == 0
|
||||
assert template.find_slot('DepthStencil') == depth_stencil_slot+1 # DepthStencil moved back by 1
|
||||
assert template.get_slot_count() == slot_count+1
|
||||
|
||||
# verify the change is saved
|
||||
template.save()
|
||||
saved_tamplate = PassTemplate(template.file_path)
|
||||
assert saved_tamplate.find_slot(new_slot['Name']) == 0
|
||||
assert saved_tamplate.get_slot_count() == slot_count+1
|
||||
|
||||
def test_PassTemplate_InsertSlot_AtEnd_Success(pass_slots_template, new_slot):
|
||||
template = pass_slots_template
|
||||
slot_count = template.get_slot_count()
|
||||
depth_stencil_slot = template.find_slot('DepthStencil')
|
||||
template.insert_slot(slot_count, new_slot)
|
||||
assert template.find_slot(new_slot['Name']) == slot_count
|
||||
assert template.find_slot('DepthStencil') == depth_stencil_slot
|
||||
assert template.get_slot_count() == slot_count+1
|
||||
|
||||
# verify the change is saved
|
||||
template.save()
|
||||
saved_tamplate = PassTemplate(template.file_path)
|
||||
assert saved_tamplate.find_slot(new_slot['Name']) == slot_count
|
||||
assert saved_tamplate.get_slot_count() == slot_count+1
|
||||
|
||||
def test_PassTemplate_AddSlot_GoodSlotData_Success(pass_slots_template, new_slot):
|
||||
template = pass_slots_template
|
||||
slot_count = template.get_slot_count()
|
||||
depth_stencil_slot = template.find_slot('DepthStencil')
|
||||
template.add_slot(new_slot)
|
||||
assert template.find_slot(new_slot['Name']) == slot_count
|
||||
assert template.find_slot('DepthStencil') == depth_stencil_slot
|
||||
assert template.get_slot_count() == slot_count+1
|
||||
|
||||
# verify the change is saved
|
||||
template.save()
|
||||
saved_tamplate = PassTemplate(template.file_path)
|
||||
assert saved_tamplate.find_slot(new_slot['Name']) == slot_count
|
||||
assert saved_tamplate.get_slot_count() == slot_count+1
|
||||
|
||||
def test_PassTemplate_InsertSlot_OutOfRange_AppendSuccess(pass_slots_template, new_slot):
|
||||
template = pass_slots_template
|
||||
slot_count = template.get_slot_count()
|
||||
template.insert_slot(slot_count+3, new_slot)
|
||||
assert template.find_slot(new_slot['Name']) == slot_count
|
||||
assert template.get_slot_count() == slot_count+1
|
||||
|
||||
def test_PassTemplate_AddDuplicateSlot_ExceptionThrown(pass_slots_template, new_slot):
|
||||
template = pass_slots_template
|
||||
slot_count = template.get_slot_count()
|
||||
template.add_slot(new_slot)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
template.insert_slot(0, new_slot)
|
||||
with pytest.raises(ValueError):
|
||||
template.add_slot(new_slot)
|
||||
|
||||
def test_PassTemplate_InsertOrAddSlot_WithBadSlotData_ExceptionThrown(pass_slots_template):
|
||||
template = pass_slots_template
|
||||
slot_count = template.get_slot_count()
|
||||
bad_slot = json.loads('{\"slot\": \"xxx\"}')
|
||||
|
||||
with pytest.raises(KeyError):
|
||||
template.insert_slot(0, bad_slot)
|
||||
with pytest.raises(KeyError):
|
||||
template.add_slot(bad_slot)
|
||||
|
||||
def test_PassReqeuest_Initialize_WithExistPassReqeuestFromPassTemplate_Success(pass_requests_template):
|
||||
template = pass_requests_template
|
||||
request = PassRequest(template.get_pass_request('OpaquePass'))
|
||||
connection_count = request.get_connection_count()
|
||||
assert connection_count == 2
|
||||
|
||||
def test_PassTemplate_GetPassRequest_NotExist_ReturnNull(pass_requests_template):
|
||||
assert not pass_requests_template.get_pass_request('NotExistPass')
|
||||
|
||||
def test_PassReqeuest_AddConnection_WithExistingConnections_Success(pass_requests_template, new_connection):
|
||||
template = pass_requests_template
|
||||
request = PassRequest(template.get_pass_request('OpaquePass'))
|
||||
connection_count = request.get_connection_count()
|
||||
request.add_connection(new_connection)
|
||||
connection_count += 1
|
||||
assert request.get_connection_count() == connection_count
|
||||
|
||||
# verify changes are saved
|
||||
template.save()
|
||||
saved_tamplate = PassTemplate(template.file_path)
|
||||
saved_request = PassRequest(saved_tamplate.get_pass_request('OpaquePass'))
|
||||
assert saved_request.get_connection_count() == connection_count
|
||||
|
||||
def test_PassReqeuest_AddConnection_WithNoExistingConnections_Success(pass_requests_template, new_connection):
|
||||
template = pass_requests_template
|
||||
request = PassRequest(template.get_pass_request('ImGuiPass'))
|
||||
assert request.get_connection_count() == 0
|
||||
request.add_connection(new_connection)
|
||||
assert request.get_connection_count() == 1
|
||||
|
||||
# verify changes are saved
|
||||
template.save()
|
||||
saved_tamplate = PassTemplate(template.file_path)
|
||||
saved_request = PassRequest(saved_tamplate.get_pass_request('ImGuiPass'))
|
||||
assert saved_request.get_connection_count() == 1
|
||||
|
||||
def test_PassReqeuest_AddConnection_WithDuplicatedName_ExceptionThrown(pass_requests_template, new_connection):
|
||||
template = pass_requests_template
|
||||
request = PassRequest(template.get_pass_request('OpaquePass'))
|
||||
request.add_connection(new_connection)
|
||||
with pytest.raises(ValueError):
|
||||
request.add_connection(new_connection)
|
||||
|
||||
def test_PassReqeuest_AddConnect_BadConnectionData_ExceptionThrown(pass_requests_template, new_connection):
|
||||
template = pass_requests_template
|
||||
request = PassRequest(template.get_pass_request('OpaquePass'))
|
||||
bad_connection = json.loads('{\"xxx\": \"xxx\"}')
|
||||
with pytest.raises(KeyError):
|
||||
request.add_connection(bad_connection)
|
||||
|
||||
def test_PassTemplate_InsertSlot_ToEmptyList_Success(pass_requests_template, new_slot):
|
||||
template = pass_requests_template
|
||||
# test insert slot function to pass template which doesn't have any slots
|
||||
slot_count = template.get_slot_count()
|
||||
assert slot_count == 0
|
||||
|
||||
assert template.find_slot(new_slot['Name'])==-1
|
||||
pass_requests_template.insert_slot(0, new_slot)
|
||||
assert template.find_slot(new_slot['Name']) == 0
|
||||
assert template.get_slot_count() == 1
|
||||
|
||||
# verify changes are saved
|
||||
template.save()
|
||||
saved_tamplate = PassTemplate(template.file_path)
|
||||
assert saved_tamplate.get_slot_count() == 1
|
||||
|
||||
def test_PassTempalte_InsertPassRequest_ToEmptyList_Success(pass_slots_template, new_pass_request):
|
||||
template = pass_slots_template
|
||||
# test insert pass function to pass template which doesn't have any pass requests
|
||||
pass_count = template.get_pass_count()
|
||||
assert pass_count == 0
|
||||
template.insert_pass_request(0, new_pass_request)
|
||||
assert template.find_pass(new_pass_request['Name']) == 0
|
||||
assert template.get_pass_count() == 1
|
||||
|
||||
# verify changes are saved
|
||||
template.save()
|
||||
saved_tamplate = PassTemplate(template.file_path)
|
||||
assert saved_tamplate.get_pass_count() == 1
|
||||
|
||||
def test_PassTemplate_Save_Success(pass_requests_template):
|
||||
pass_requests_template.save()
|
||||
saved_tamplate = PassTemplate(pass_requests_template.file_path)
|
||||
assert os.path.exists(pass_requests_template.file_path)
|
||||
assert os.path.exists(pass_requests_template.file_path +'.backup')
|
||||
@@ -0,0 +1,53 @@
|
||||
"""
|
||||
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
its licensors.
|
||||
|
||||
For complete copyright and license terms please see the LICENSE at the root of this
|
||||
distribution (the "License"). All use of this software is governed by the License,
|
||||
or, if provided, by the license below or the license accompanying this file. Do not
|
||||
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
Unit tests for utils.py
|
||||
"""
|
||||
import pytest
|
||||
import os
|
||||
import atom_rpi_tools.utils as utils
|
||||
|
||||
|
||||
def test_FindOrCopyFile_DestFileNotExist_CopySuccess(tmpdir):
|
||||
# created dir and copied
|
||||
filename = 'pass_requests.json'
|
||||
source_path = os.path.join(os.path.dirname(__file__), 'testdata/', filename)
|
||||
dest_path = os.path.join(tmpdir, 'testdata/', 'pass_requests.json')
|
||||
assert not os.path.exists(dest_path)
|
||||
utils.find_or_copy_file(dest_path, source_path)
|
||||
assert os.path.exists(dest_path)
|
||||
source_size = os.path.getsize(source_path)
|
||||
dest_size = os.path.getsize(dest_path)
|
||||
assert source_size == dest_size
|
||||
|
||||
def test_FindOrCopyFile_DestFileAlreadyExists_Skip(tmpdir):
|
||||
# copy %cur_dir%/testdata/pass_requests.json to tempdir/testdata/pass_requests.json
|
||||
filename = 'pass_requests.json'
|
||||
source_path = os.path.join(os.path.dirname(__file__), 'testdata/', filename)
|
||||
dest_path = os.path.join(tmpdir, 'testdata/', 'pass_requests.json')
|
||||
utils.find_or_copy_file(dest_path, source_path)
|
||||
|
||||
# skip if dest_path already exists
|
||||
assert os.path.exists(dest_path)
|
||||
before_size = os.path.getsize(dest_path)
|
||||
source_path = os.path.join(os.path.dirname(__file__), 'testdata/', 'pass_slots.json')
|
||||
before_source_size = os.path.getsize(source_path)
|
||||
assert before_size != source_path
|
||||
utils.find_or_copy_file(dest_path, source_path)
|
||||
after_size = os.path.getsize(dest_path)
|
||||
assert before_size == after_size
|
||||
|
||||
|
||||
def test_FindOrCopyFile_SourceFileNotExists_ExceptionThrown(tmpdir):
|
||||
# report error if source doesn't exist
|
||||
bad_source_path = 'notexist.dat'
|
||||
dest_path = os.path.join(tmpdir, 'notexist.dat')
|
||||
with pytest.raises(ValueError):
|
||||
utils.find_or_copy_file(dest_path, bad_source_path)
|
||||
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"Type": "JsonSerialization",
|
||||
"Version": 1,
|
||||
"ClassName": "PassAsset",
|
||||
"ClassData": {
|
||||
"PassTemplate": {
|
||||
"Name": "PipelineTemplate",
|
||||
"PassClass": "ParentPass",
|
||||
"PassRequests": [
|
||||
{
|
||||
"Name": "OpaquePass",
|
||||
"TemplateName": "OpaquePassTemplate",
|
||||
"Connections": [
|
||||
{
|
||||
"LocalSlot": "DepthStencil",
|
||||
"AttachmentRef": {
|
||||
"Pass": "Parent",
|
||||
"Attachment": "DepthStencil"
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocalSlot": "ColorInputOutput",
|
||||
"AttachmentRef": {
|
||||
"Pass": "Parent",
|
||||
"Attachment": "ColorInputOutput"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "TransparentPass",
|
||||
"TemplateName": "TransparentPassTemplate",
|
||||
"Enabled": true,
|
||||
"Connections": [
|
||||
{
|
||||
"LocalSlot": "DepthStencil",
|
||||
"AttachmentRef": {
|
||||
"Pass": "OpaquePass",
|
||||
"Attachment": "DepthStencil"
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocalSlot": "ColorInputOutput",
|
||||
"AttachmentRef": {
|
||||
"Pass": "OpaquePass",
|
||||
"Attachment": "Color"
|
||||
}
|
||||
}
|
||||
],
|
||||
"PassData": {
|
||||
"$type": "RasterPassData",
|
||||
"DrawListTag": "transparent",
|
||||
"DrawListSortType": "KeyThenReverseDepth",
|
||||
"PipelineViewTag": "MainCamera",
|
||||
"PassSrgAsset": {
|
||||
"FilePath": "shaderlib/atom/features/pbr/transparentpasssrg.azsli:PassSrg"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "AuxGeomPass",
|
||||
"TemplateName": "AuxGeomPassTemplate",
|
||||
"Enabled": true,
|
||||
"Connections": [
|
||||
{
|
||||
"LocalSlot": "DepthStencil",
|
||||
"AttachmentRef": {
|
||||
"Pass": "OpaquePass",
|
||||
"Attachment": "DepthStencil"
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocalSlot": "ColorInputOutput",
|
||||
"AttachmentRef": {
|
||||
"Pass": "TransparentPass",
|
||||
"Attachment": "ColorInputOutput"
|
||||
}
|
||||
}
|
||||
],
|
||||
"PassData": {
|
||||
"$type": "RasterPassData",
|
||||
"DrawListTag": "auxgeom",
|
||||
"PipelineViewTag": "MainCamera"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "2DPass",
|
||||
"TemplateName": "UIPassTemplate",
|
||||
"Enabled": true,
|
||||
"Connections": [
|
||||
{
|
||||
"LocalSlot": "ColorInputOutput",
|
||||
"AttachmentRef": {
|
||||
"Pass": "TransparentPass",
|
||||
"Attachment": "ColorInputOutput"
|
||||
}
|
||||
}
|
||||
],
|
||||
"PassData": {
|
||||
"$type": "RasterPassData",
|
||||
"DrawListTag": "2dpass",
|
||||
"PipelineViewTag": "MainCamera"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "ImGuiPass",
|
||||
"TemplateName": "ImGuiPassTemplate",
|
||||
"PassData": {
|
||||
"$type": "ImGuiPassData",
|
||||
"IsDefaultImGui": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"Type": "JsonSerialization",
|
||||
"Version": 1,
|
||||
"ClassName": "PassAsset",
|
||||
"ClassData": {
|
||||
"PassTemplate": {
|
||||
"Name": "PipelineTemplate",
|
||||
"PassClass": "ParentPass",
|
||||
"Slots": [
|
||||
{
|
||||
"Name": "DepthStencil",
|
||||
"SlotType": "InputOutput"
|
||||
},
|
||||
{
|
||||
"Name": "ColorInputOutput",
|
||||
"SlotType": "InputOutput"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"Type": "JsonSerialization",
|
||||
"Version": 1,
|
||||
"ClassData": {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
"""
|
||||
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
its licensors.
|
||||
|
||||
For complete copyright and license terms please see the LICENSE at the root of this
|
||||
distribution (the "License"). All use of this software is governed by the License,
|
||||
or, if provided, by the license below or the license accompanying this file. Do not
|
||||
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
"""
|
||||
import os.path
|
||||
from os import path
|
||||
import shutil
|
||||
import json
|
||||
|
||||
|
||||
def find_or_copy_file(destFilePath, sourceFilePath):
|
||||
if path.exists(destFilePath):
|
||||
return
|
||||
if not path.exists(sourceFilePath):
|
||||
raise ValueError('find_or_copy_file: source file [', sourceFilePath, '] doesn\'t exist')
|
||||
return
|
||||
|
||||
dstDir = path.dirname(destFilePath)
|
||||
if not path.isdir(dstDir):
|
||||
os.makedirs(dstDir)
|
||||
shutil.copyfile(sourceFilePath, destFilePath)
|
||||
|
||||
def load_json_file(filePath):
|
||||
file_stream = open(filePath, "r")
|
||||
return json.load(file_stream)
|
||||
@@ -0,0 +1,33 @@
|
||||
"""
|
||||
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
its licensors.
|
||||
|
||||
For complete copyright and license terms please see the LICENSE at the root of this
|
||||
distribution (the "License"). All use of this software is governed by the License,
|
||||
or, if provided, by the license below or the license accompanying this file. Do not
|
||||
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
"""
|
||||
import os
|
||||
import platform
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
PYTHON_64 = platform.architecture()[0] == '64bit'
|
||||
|
||||
if __name__ == '__main__':
|
||||
if not PYTHON_64:
|
||||
raise RuntimeError("32-bit Python is not a supported platform.")
|
||||
|
||||
with open(os.path.join(PROJECT_ROOT, 'README.txt')) as f:
|
||||
long_description = f.read()
|
||||
|
||||
setup(
|
||||
name="atom_rpi_tools",
|
||||
version="1.0.0",
|
||||
description='Python interface to Atom RPI tools',
|
||||
long_description=long_description,
|
||||
packages=find_packages(exclude=['tests'])
|
||||
)
|
||||
Reference in New Issue
Block a user