Skip to content

Try building like this instead #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 31 commits into
base: main
Choose a base branch
from
Open
Changes from 2 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
002c222
Fix all Asset Replacement Endpoints + Add Tests
SmokedPete Mar 19, 2025
1d8b016
Merge branch 'dev/ptrottier/fix_api_endpoint' into 'main'
SmokedPete Mar 19, 2025
4153ebd
Redirect documentation pages that were moved to avoid 404 errors + Up…
SmokedPete Mar 19, 2025
d989a49
Merge branch 'dev/ptrottier/docs_redirect' into 'main'
SmokedPete Mar 19, 2025
a770d52
REMIX-3869: Drag and dropping texture brings up error window with upp…
Mar 20, 2025
e130280
Merge branch 'dev/sgillard/upper_case_text_bug' into 'main'
Mar 20, 2025
79a4b02
Add REST API Tests for all endpoints
SmokedPete Mar 21, 2025
3f65930
Merge branch 'dev/ptrottier/add_rest_api_tests' into 'main'
SmokedPete Mar 21, 2025
95711b6
Fixed min specs in docs + URL for Home page
SmokedPete Mar 21, 2025
c71751d
Merge branch 'dev/ptrottier/fix_docs' into 'main'
SmokedPete Mar 21, 2025
34226a5
REMIX-3900: Fix for texture preview for height maps
Mar 21, 2025
9d59a97
Merge branch 'dev/sgillard/height_map_bug' into 'main'
Mar 21, 2025
79f9545
Cleanup legacy extensions
SmokedPete Mar 22, 2025
3b2425a
Merge branch 'dev/ptrottier/cleanup_extensions' into 'main'
SmokedPete Mar 22, 2025
fef9f83
REMIX-3581: Fix multi-selection visibility toggle to have consistent …
SmokedPete Mar 24, 2025
025b3b0
Merge branch 'dev/ptrottier/multiselect_toggle' into 'main'
SmokedPete Mar 24, 2025
8c2f1a2
[REMIX-3891] Material Property Widget control with Stage Manager Mate…
Mar 24, 2025
b657a6b
Merge branch 'dev/sfitzpatrick/mat_properties_control_with_stage_mana…
Mar 24, 2025
9a2c9ae
REMIX-4043: Fixed layer creation transferring metadata from parent layer
SmokedPete Mar 26, 2025
691f67f
Merge branch 'dev/ptrottier/fix_layer_creation' into 'main'
SmokedPete Mar 26, 2025
00736bc
[REMIX-4073] Fix material override saving location
Apr 1, 2025
4a19fd4
Merge branch 'dev/sfitzpatrick/fix_mat_override_saving' into 'main'
Apr 1, 2025
7a003d5
Fixed Toolkit Build Dependencies
SmokedPete Apr 1, 2025
a2b7ad0
Merge branch 'dev/ptrottier/fix_build' into 'main'
SmokedPete Apr 1, 2025
a0aa678
Try building like this instead
nvzkupu Mar 17, 2025
c13d881
Build on changes to github actions files
nvzkupu Mar 17, 2025
0b83141
Checkout the repo
nvzkupu Mar 17, 2025
2cf9ab1
Update linux build config as well
nvzkupu Mar 17, 2025
66e7bba
Add timeout of 1 hour
nvzkupu Mar 18, 2025
281242e
whitespace
nvzkupu Mar 18, 2025
ed4c654
test: comment out steps that need app to run
nxkb Apr 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Fixed tests for the `omni.flux.validator.mass.widget` extension
- Fixed `get_texture_material_inputs` API endpoint for Kit 106.5
- Fixed `is_valid_texture_prim` API validator for Kit 106.5

### Removed

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
version = "2.5.3"
version = "2.5.4"
authors =["Damien Bataille <[email protected]>"]
title = "NVIDIA RTX Remix Asset Replacements extension for the StageCraft"
description = "Extension that works on asset replacement data for NVIDIA RTX Remix StageCraft App"
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Changelog
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [2.5.4]
## Fixed
- Fixed some validators and core functions with bugs discovered during testing

## [2.5.3]
## Changed
- rename trex prim utility names for clarity
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@
from lightspeed.trex.utils.common.prim_utils import is_material_prototype
from omni.flux.utils.common import path_utils
from omni.flux.utils.common.omni_url import OmniUrl
from pxr import Sdf
from pxr import Sdf, Usd


class AssetReplacementsValidators:
@@ -46,7 +46,7 @@ def is_valid_prim(cls, prim_path: str, context_name: str):

@classmethod
def is_valid_mesh(cls, prim_path: str):
if not re.match(constants.REGEX_MESH_PATH, prim_path):
if not re.match(constants.REGEX_MESH_PATH, prim_path) and not re.match(constants.REGEX_IN_MESH_PATH, prim_path):
raise ValueError(f"The prim path does not point to a model: {prim_path}")

return prim_path
@@ -65,7 +65,11 @@ def is_valid_material(cls, prim_path: str, context_name: str):
@classmethod
def has_at_least_one_ref(cls, prim_path: str, context_name: str):
prim = omni.usd.get_context(context_name).get_stage().GetPrimAtPath(prim_path)
references = omni.usd.get_composed_references_from_prim(prim)

if not prim:
raise ValueError(f"The prim path does not exist in the current stage: {prim_path}")

_, references = cls.get_prim_references(prim_path, context_name)

if not references:
raise ValueError("The selected prim has no references")
@@ -75,12 +79,16 @@ def has_at_least_one_ref(cls, prim_path: str, context_name: str):
@classmethod
def ref_exists_in_prim(cls, asset_path: Path, layer_id: Path, prim_path: str, context_name: str):
prim = omni.usd.get_context(context_name).get_stage().GetPrimAtPath(prim_path)
references = omni.usd.get_composed_references_from_prim(prim)

if not prim:
raise ValueError(f"The prim path does not exist in the current stage: {prim_path}")

_, references = cls.get_prim_references(prim_path, context_name)

for reference, layer in references:
if (
OmniUrl(reference.assetPath).path == OmniUrl(asset_path).path
and OmniUrl(layer.identifier).path == OmniUrl(layer_id).path
OmniUrl(reference.assetPath).path.lower() == OmniUrl(asset_path).path.lower()
and OmniUrl(layer.identifier).path.lower() == OmniUrl(layer_id).path.lower()
):
return prim_path

@@ -120,3 +128,38 @@ def layer_is_in_project(cls, layer_id: Path | None, context_name: str):
raise ValueError(f"The layer is not present in the loaded project's layer stack: {layer_id}")

return layer_id

@classmethod
def get_prim_references(
cls, prim_path: str, context_name: str
) -> tuple[Usd.Prim, list[tuple[Sdf.Reference, Sdf.Layer]]]:
stage = omni.usd.get_context(context_name).get_stage()
prim = stage.GetPrimAtPath(str(prim_path))
if not prim:
# Invalid prim path
return prim, []

introducing_prim = prim

# If the asset has a reference, it should have more than 1 prim in the stack
prim_stack = introducing_prim.GetPrimStack()
while len(prim_stack) <= 1:
introducing_prim = introducing_prim.GetParent()
if not introducing_prim:
# No reference found
return prim, []
prim_stack = introducing_prim.GetPrimStack()

references = omni.usd.get_composed_references_from_prim(introducing_prim)

# If no references are found, try to build a reference using the prim stack
if not references:
external_layers = [i.layer for i in introducing_prim.GetPrimStack() if i.layer not in stage.GetLayerStack()]
if external_layers:
reference_layer = external_layers[-1]
introducing_layer = prim_stack[0].layer
relative_path = str(Path(reference_layer.realPath).relative_to(Path(introducing_layer.realPath).parent))

references = [(Sdf.Reference(relative_path), introducing_layer)]

return introducing_prim, references
Original file line number Diff line number Diff line change
@@ -47,8 +47,6 @@
from omni.usd.commands import remove_prim_spec as _remove_prim_spec
from pxr import Sdf, Usd, UsdGeom, UsdShade, UsdSkel

from .data_models import DefaultAssetDirectory as _DefaultAssetDirectory

if typing.TYPE_CHECKING:
from lightspeed.trex.selection_tree.shared.widget.selection_tree.model import ItemInstance as _ItemInstance
from lightspeed.trex.selection_tree.shared.widget.selection_tree.model import (
@@ -58,6 +56,8 @@
from .data_models import (
AppendReferenceRequestModel,
AssetPathResponseModel,
AssetReplacementsValidators,
DefaultAssetDirectory,
GetPrimsQueryModel,
GetTexturesQueryModel,
PrimInstancesPathParamModel,
@@ -129,41 +129,48 @@ def get_textures_with_data_model(
self, params: PrimTexturesPathParamModel, query: GetTexturesQueryModel
) -> TexturesResponseModel:
return TexturesResponseModel(
textures=self.get_textures_from_material_path(params.asset_path, texture_types=query.texture_types)
textures=self.get_textures_from_material_path(
params.asset_path,
texture_types=(
{_TextureTypes[texture_type.value] for texture_type in query.texture_types}
if query.texture_types
else None
),
)
)

def get_reference_with_data_model(self, params: PrimReferencePathParamModel) -> ReferenceResponseModel:
stage = self._context.get_stage()
prim = stage.GetPrimAtPath(str(params.asset_path))

references = omni.usd.get_composed_references_from_prim(prim)

introducing_prim, references = AssetReplacementsValidators.get_prim_references(
params.asset_path, self._context_name
)
return ReferenceResponseModel(
reference_paths=[(str(prim.GetPath()), (str(ref.assetPath), layer.identifier)) for ref, layer in references]
reference_paths=[
(str(introducing_prim.GetPath()), (str(ref.assetPath), layer.identifier)) for ref, layer in references
]
)

def replace_reference_with_data_model(
self, params: PrimReferencePathParamModel, body: ReplaceReferenceRequestModel
) -> ReferenceResponseModel:
with omni.kit.undo.group():
stage = self._context.get_stage()

edit_target_layer = stage.GetEditTarget().GetLayer()

introducing_prim, references = AssetReplacementsValidators.get_prim_references(
params.asset_path, self._context_name
)
prim_path = introducing_prim.GetPath()

if body.existing_asset_layer_id and body.existing_asset_file_path:
current_layer = Sdf.Layer.FindOrOpen(str(body.existing_asset_layer_id))
current_ref = Sdf.Reference(
assetPath=_OmniUrl(body.existing_asset_file_path).path, primPath=str(params.asset_path)
)
else:
prim = stage.GetPrimAtPath(str(params.asset_path))
references = omni.usd.get_composed_references_from_prim(prim)
current_ref, current_layer = references[0]

# Remove the existing reference
self.remove_reference(
stage, Sdf.Path(str(params.asset_path)), current_ref, current_layer, remove_if_remix_ref=False
)
self.remove_reference(stage, prim_path, current_ref, current_layer, remove_if_remix_ref=False)

# Get the new reference prim path
ref_prim_path = self.get_reference_prim_path_from_asset_path(
@@ -173,7 +180,7 @@ def replace_reference_with_data_model(
# Add the new reference prim path
reference, child_prim_path = self.add_new_reference(
stage,
Sdf.Path(str(params.asset_path)),
prim_path,
self.switch_ref_abs_to_rel_path(stage, str(body.asset_file_path)),
ref_prim_path,
edit_target_layer,
@@ -190,9 +197,11 @@ def append_reference_with_data_model(
with omni.kit.undo.group():
stage = self._context.get_stage()
edit_target_layer = stage.GetEditTarget().GetLayer()
introducing_prim, _ = AssetReplacementsValidators.get_prim_references(params.asset_path, self._context_name)

reference, child_prim_path = self.add_new_reference(
stage,
Sdf.Path(str(params.asset_path)),
introducing_prim.GetPath(),
self.switch_ref_abs_to_rel_path(stage, str(body.asset_file_path)),
self.get_ref_default_prim_tag(),
edit_target_layer,
@@ -203,7 +212,7 @@ def append_reference_with_data_model(
)

def get_default_output_directory_with_data_model(
self, directory: _DefaultAssetDirectory = _DefaultAssetDirectory.INGESTED
self, directory: DefaultAssetDirectory = DefaultAssetDirectory.INGESTED
) -> AssetPathResponseModel:
stage = self._context.get_stage()
if not stage:
@@ -261,23 +270,23 @@ def get_textures_from_material_path(
if not shader:
return textures

texture_type_names = None
if texture_types is not None:
texture_type_names = [_get_texture_type_attribute(texture_type) for texture_type in texture_types]

for shader_input in shader.GetInputs():
# Make sure the input matches the filter if set
if texture_types is not None:
texture_type_names = [_get_texture_type_attribute(texture_type) for texture_type in texture_types]
if shader_input.GetBaseName() not in texture_type_names:
continue
if texture_type_names is not None and shader_input.GetFullName() not in texture_type_names:
continue
# Make sure the input expects an asset
if shader_input.GetTypeName() != Sdf.ValueTypeNames.Asset:
continue
# Make sure the asset is a supported texture
texture_asset_path = shader_input.Get().resolvedPath
if _OmniUrl(texture_asset_path).suffix.lower() not in _SUPPORTED_TEXTURE_EXTENSIONS:
continue
# Build the full property path
texture_input_path = shader_prim.GetPath().AppendProperty(shader_input.GetFullName())
# Store the texture property and the asset path
textures.append((str(texture_input_path), str(texture_asset_path)))
textures.append((str(shader_input.GetAttr().GetPath()), str(texture_asset_path)))

return textures

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
version = "1.3.0"
version = "1.3.1"
authors =["Pierre-Oliver Trottier <[email protected]>"]
title = "NVIDIA RTX Remix Asset Replacements Service extension"
description = "Extension that exposes microservices for asset replacement data for NVIDIA RTX Remix"
@@ -17,3 +17,21 @@ preview_image = "data/preview.png"

[[python.module]]
name = "lightspeed.trex.asset_replacements.service"

[[test]]
dependencies = [
"lightspeed.trex.tests.dependencies",
"omni.flux.utils.widget",
"omni.kit.pip_archive", # For fastapi
"omni.services.core",
]

stdoutFailPatterns.exclude = [
"*[omni.kit.registry.nucleus.utils.common] Skipping deletion of:*",
]

[[test]]
name = "startup"
dependencies = [
"lightspeed.trex.tests.dependencies",
]
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Changelog
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [1.3.1]
### Added
- Added test dependencies
- Added e2e tests for the service

## [1.3.0]
### Added
- Added endpoints to get default model and texture directories
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""
* SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
"""

from .e2e.test_service import TestAssetReplacementsService
Original file line number Diff line number Diff line change
@@ -0,0 +1,351 @@
"""
* SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
"""

import unittest
from pathlib import Path

import carb
import omni.usd
from fastapi.testclient import TestClient
from omni.flux.service.factory import get_instance as get_service_factory_instance
from omni.flux.utils.widget.resources import get_test_data
from omni.kit.test import AsyncTestCase
from omni.kit.test_suite.helpers import open_stage
from omni.services.core import main


class TestAssetReplacementsService(AsyncTestCase):
# Before running each test
async def setUp(self):
self.project_path = get_test_data("usd/project_example/combined.usda")

self.context = omni.usd.get_context()
await open_stage(self.project_path)

factory = get_service_factory_instance()

# Register the service in the app
self.service = factory.get_plugin_from_name("AssetReplacementsService")()
main.register_router(router=self.service.router, prefix=self.service.prefix)

# Setup a test client to send requests
host = carb.settings.get_settings().get("/exts/omni.services.transport.server.http/host")
port = carb.settings.get_settings().get("/exts/omni.services.transport.server.http/port")
self.client = TestClient(main.get_app(), base_url=f"http://{host}:{port}{self.service.prefix}")

# After running each test
async def tearDown(self):
main.deregister_router(router=self.service.router, prefix=self.service.prefix)

self.client = None
self.service = None

if self.context.can_close_stage():
await self.context.close_stage_async()

self.context = None
self.project_path = None

async def test_get_default_model_asset_path_directory_returns_expected_response(self):
# Arrange
expected_value = (Path(get_test_data("usd/project_example")) / "assets" / "models").as_posix()

# Act
response = self.client.get("/default-directory/models")

# Assert
self.assertTrue(response.is_success)
self.assertEqual(str(response.json()).lower(), str({"asset_path": expected_value}).lower())

async def test_get_default_texture_asset_path_directory_returns_expected_response(self):
# Arrange
expected_value = (Path(get_test_data("usd/project_example/")) / "assets" / "textures").as_posix()

# Act
response = self.client.get("/default-directory/textures")

# Assert
self.assertTrue(response.is_success)
self.assertEqual(str(response.json()).lower(), str({"asset_path": expected_value}).lower())

async def test_get_default_ingested_asset_path_directory_returns_expected_response(self):
# Arrange
expected_value = (Path(get_test_data("usd/project_example")) / "assets" / "ingested").as_posix()

# Act
response = self.client.get("/default-directory")

# Assert
self.assertTrue(response.is_success)
self.assertEqual(str(response.json()).lower(), str({"asset_path": expected_value}).lower())

async def test_get_assets_returns_expected_response(self):
for index, test_data in enumerate(
[
(
"",
{
"asset_paths": [
"/RootNode/lights/light_9907D0B07D040077",
"/RootNode/lights/light_EDF9B59568FD1142",
"/RootNode/lights/light_0FBF0D906770A019",
"/RootNode/meshes/mesh_0AB745B8BEE1F16B/mesh",
"/RootNode/meshes/mesh_CED45075A077A49A/mesh",
"/RootNode/meshes/mesh_BAC90CAA733B0859/ref_c89e0497f4ff4dc4a7b70b79c85692da/Cube",
"/RootNode/meshes/mesh_BAC90CAA733B0859/ref_c89e0497f4ff4dc4a7b70b79c85692da/Cube_01",
"/RootNode/Looks/mat_BC868CE5A075ABB1",
]
},
),
(
"asset_hashes=CED45075A077A49A&asset_hashes=BAC90CAA733B0859",
{
"asset_paths": [
"/RootNode/meshes/mesh_CED45075A077A49A/mesh",
"/RootNode/meshes/mesh_BAC90CAA733B0859/ref_c89e0497f4ff4dc4a7b70b79c85692da/Cube",
"/RootNode/meshes/mesh_BAC90CAA733B0859/ref_c89e0497f4ff4dc4a7b70b79c85692da/Cube_01",
]
},
),
(
"asset_types=lights",
{
"asset_paths": [
"/RootNode/lights/light_9907D0B07D040077",
"/RootNode/lights/light_EDF9B59568FD1142",
"/RootNode/lights/light_0FBF0D906770A019",
]
},
),
]
):
params, expected_response = test_data
with self.subTest(name=f"test_{index}"):
# Arrange
pass

# Act
response = self.client.get(f"/?{params}")

# Assert
self.assertTrue(response.is_success)
self.assertEqual(response.json(), expected_response)

async def test_get_model_instances_returns_expected_response(self):
# Arrange
pass

# Act
response = self.client.get("/%2FRootNode%2Fmeshes%2Fmesh_CED45075A077A49A%2Fmesh/instances")

# Assert
self.assertTrue(response.is_success)
self.assertEqual(response.json(), {"asset_paths": ["/RootNode/instances/inst_CED45075A077A49A_0/mesh"]})

async def test_get_material_textures_returns_expected_response(self):
# Arrange
expected_diffuse_value = str(
Path(get_test_data("usd/project_example"))
/ ".deps"
/ "captures"
/ "materials"
/ "textures"
/ "BC868CE5A075ABB1.dds"
)
expected_metallic_value = str(
Path(get_test_data("usd/project_example"))
/ "sources"
/ "textures"
/ "T_MetalPanelWall_HeavyRust_metallic.png"
)
expected_normal_value = str(
Path(get_test_data("usd/project_example"))
/ "sources"
/ "textures"
/ "T_MetalPanelWall_HeavyRust_normal.png"
)
expected_roughness_value = str(
Path(get_test_data("usd/project_example"))
/ "sources"
/ "textures"
/ "T_MetalPanelWall_HeavyRust_roughness.png"
)

# Act
response = self.client.get("/%2FRootNode%2FLooks%2Fmat_BC868CE5A075ABB1/textures")

# Assert
self.assertTrue(response.is_success)
self.assertEqual(
str(response.json()).lower(),
str(
{
"textures": [
["/RootNode/Looks/mat_BC868CE5A075ABB1/Shader.inputs:diffuse_texture", expected_diffuse_value],
[
"/RootNode/Looks/mat_BC868CE5A075ABB1/Shader.inputs:metallic_texture",
expected_metallic_value,
],
["/RootNode/Looks/mat_BC868CE5A075ABB1/Shader.inputs:normalmap_texture", expected_normal_value],
[
"/RootNode/Looks/mat_BC868CE5A075ABB1/Shader.inputs:reflectionroughness_texture",
expected_roughness_value,
],
]
}
).lower(),
)

async def test_get_material_textures_args_returns_expected_response(self):
# Arrange
expected_diffuse_value = str(
Path(get_test_data("usd/project_example"))
/ ".deps"
/ "captures"
/ "materials"
/ "textures"
/ "BC868CE5A075ABB1.dds"
)
expected_normal_value = str(
Path(get_test_data("usd/project_example"))
/ "sources"
/ "textures"
/ "T_MetalPanelWall_HeavyRust_normal.png"
)

# Act
response = self.client.get(
"/%2FRootNode%2FLooks%2Fmat_BC868CE5A075ABB1/textures?texture_types=DIFFUSE&texture_types=NORMAL_OGL"
)

# Assert
self.assertTrue(response.is_success)
self.assertEqual(
str(response.json()).lower(),
str(
{
"textures": [
["/RootNode/Looks/mat_BC868CE5A075ABB1/Shader.inputs:diffuse_texture", expected_diffuse_value],
["/RootNode/Looks/mat_BC868CE5A075ABB1/Shader.inputs:normalmap_texture", expected_normal_value],
]
}
).lower(),
)

async def test_get_asset_file_paths_returns_expected_response(self):
# Arrange
expected_relative_path = str(Path("meshes") / "mesh_CED45075A077A49A.usda")
expected_layer_path = str(Path(get_test_data("usd/project_example")) / ".deps" / "captures" / "capture.usda")

# Act
response = self.client.get("/%2FRootNode%2Fmeshes%2Fmesh_CED45075A077A49A%2Fmesh/file-paths")

# Assert
self.assertTrue(response.is_success)
self.assertEqual(
str(response.json()).lower(),
str(
{
"reference_paths": [
["/RootNode/meshes/mesh_CED45075A077A49A", [expected_relative_path, expected_layer_path]]
]
}
).lower(),
)

async def test_append_asset_file_path_should_add_new_reference(self):
# Arrange
append_asset_path = str(Path(get_test_data("usd/project_example/ingested_assets/output/good/cube.usda")))
mod_layer = str(Path(get_test_data("usd/project_example/combined.usda"))).replace("\\", "\\\\")

# Act
response = self.client.post(
"/%2FRootNode%2Fmeshes%2Fmesh_CED45075A077A49A%2Fmesh/file-paths",
json={
"asset_file_path": append_asset_path,
"force": False,
},
)

# Assert
self.assertTrue(response.is_success)
self.assertRegex(
str(response.json()).replace("\\\\", "\\").lower(),
f"{{'reference_paths': \\[\\['/RootNode/meshes/mesh_CED45075A077A49A/ref_[0-9a-z]{{32}}', "
f"\\['ingested_assets\\\\output\\\\good\\\\cube.usda', '{mod_layer}'\\]\\]\\]}}".lower(),
)

async def test_replace_asset_file_path_no_ref_should_replace_first_ref(self):
# Arrange
replace_asset_path = str(Path(get_test_data("usd/project_example/ingested_assets/output/good/cube.usda")))
mod_layer = str(Path(get_test_data("usd/project_example/combined.usda"))).replace("\\", "\\\\")

# Act
response = self.client.put(
"/%2FRootNode%2Fmeshes%2Fmesh_0AB745B8BEE1F16B%2Fmesh/file-paths",
json={
"asset_file_path": replace_asset_path,
"force": False,
},
)

# Assert
self.assertTrue(response.is_success)
self.assertRegex(
str(response.json()).replace("\\\\", "\\").lower(),
f"{{'reference_paths': \\[\\['/RootNode/meshes/mesh_0AB745B8BEE1F16B/ref_[0-9a-z]{{32}}', "
f"\\['ingested_assets\\\\output\\\\good\\\\cube.usda', '{mod_layer}'\\]\\]\\]}}".lower(),
)

async def test_replace_asset_file_path_with_ref_should_replace_expected_ref(self):
# Arrange
original_asset_path = "sources\\\\cube.usda"
original_layer = str(Path(get_test_data("usd/project_example/replacements.usda"))).replace("\\", "\\\\")

replace_asset_path = str(Path(get_test_data("usd/project_example/ingested_assets/output/good/cube.usda")))
mod_layer = str(Path(get_test_data("usd/project_example/combined.usda"))).replace("\\", "\\\\")

# Act
response = self.client.put(
"/%2FRootNode%2Fmeshes%2Fmesh_BAC90CAA733B0859%2Fref_c89e0497f4ff4dc4a7b70b79c85692da%2FCube_01/file-paths",
json={
"existing_asset_file_path": original_asset_path,
"existing_asset_layer_id": original_layer,
"asset_file_path": replace_asset_path,
"force": False,
},
)

# Assert
self.assertTrue(response.is_success)
self.assertRegex(
str(response.json()).replace("\\\\", "\\").lower(),
f"{{'reference_paths': \\[\\['/RootNode/meshes/mesh_BAC90CAA733B0859/ref_[0-9a-z]{{32}}', "
f"\\['ingested_assets\\\\output\\\\good\\\\cube.usda', '{mod_layer}'\\]\\]\\]}}".lower(),
)

@unittest.skip("For some reason, context.get_selection().get_selected_prim_paths() locks up the test")
async def test_set_selection_updates_stage_selection(self):
# Arrange
pass

# Act
response = self.client.put("/selection/%2FRootNode%2Flights%2Flight_9907D0B07D040077")

# Assert
self.assertTrue(response.is_success)
self.assertEqual(response.json(), "OK")
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
version = "1.2.2"
version = "1.2.3"
authors =["Pierre-Olivier Trottier <ptrottier@nvidia.com>"]
title = "NVIDIA RTX Remix Texture Replacements extension for the StageCraft"
description = "Extension that works on texture replacement data for NVIDIA RTX Remix StageCraft App"
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Changelog
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [1.2.3]
## Fixed
- Fixed validator shader input logic for 106.5

## [1.2.2]
## Fixed
- Fixed shader input logic for 106.5
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@
import omni.usd
from lightspeed.trex.utils.common.asset_utils import is_asset_ingested
from omni.flux.asset_importer.core.data_models import SUPPORTED_TEXTURE_EXTENSIONS
from omni.flux.material_api import ShaderInfoAPI
from omni.flux.utils.common.omni_url import OmniUrl
from pxr import Sdf, UsdShade

@@ -38,19 +39,19 @@ def is_valid_texture_prim(cls, texture_tuple: tuple[str, Path], context_name: st
except Exception as e:
raise ValueError(f"The string is not a valid path: {property_path}") from e

usd_property = omni.usd.get_context(context_name).get_stage().GetPropertyAtPath(path)
if not usd_property:
raise ValueError(f"The property path does not exist in the current stage: {property_path}")
prim_path = path.GetPrimPath()
prim = omni.usd.get_context(context_name).get_stage().GetPrimAtPath(prim_path)
if not prim:
raise ValueError(f"The prim path does not exist in the current stage: {prim_path}")

prim = usd_property.GetPrim()
if not prim.IsA(UsdShade.Shader):
raise ValueError(f"The property path does not point to a valid USD shader property: {property_path}")

shader = UsdShade.Shader(prim)
if not shader.GetInput(usd_property.GetBaseName()):
raise ValueError(f"The property path does not point to a valid USD shader input: {property_path}")
for input_property in ShaderInfoAPI(prim).get_input_properties():
if input_property.GetName() == path.name:
return texture_tuple

return texture_tuple
raise ValueError(f"The property path does not point to a valid USD shader input: {property_path}")

@classmethod
def is_valid_texture_asset(cls, texture_tuple: tuple[str, Path], force: bool):
Original file line number Diff line number Diff line change
@@ -55,10 +55,10 @@ async def test_is_valid_texture_prim_returns_expected_value_or_raises(self):
False,
"The property path does not point to a valid USD shader input",
),
valid_prim_path: (False, "The property path does not exist in the current stage"),
invalid_prim_path: (False, "The property path does not exist in the current stage"),
valid_prim_path: (False, "The property path does not point to a valid USD shader input"),
invalid_prim_path: (False, "The property path does not point to a valid USD shader property"),
"This.Is/Not A Prim": (False, "The string is not a valid path"),
"/test/non/existent/prim": (False, "The property path does not exist in the current stage"),
"/test/non/existent/prim": (False, "The prim path does not exist in the current stage"),
}

for prim_path, expected_value in test_cases.items():
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
# Semantic Versionning is used: https://semver.org/
version = "1.0.2"
version = "1.1.0"

# Lists people or organizations that are considered the "authors" of the package.
authors = ["Pierre-Olivier Trottier <ptrottier@nvidia.com>"]
@@ -34,6 +34,7 @@ order = -1000

[dependencies]
"omni.flux.tests.settings" = {}
"omni.flux.utils.tests" = {}
"omni.kit.ui_test" = {}

[settings]
Original file line number Diff line number Diff line change
@@ -2,6 +2,10 @@

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [1.1.0]
## Added
- Added `omni.flux.utils.tests` as a dependency

## [1.0.2]
## Changed
- Update to Kit 106.5