From 346f3ae3aebdb2840847d2b1ded2181185cad200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Hern=C3=A1ndez=20Cordero?= Date: Wed, 9 Mar 2022 12:42:48 +0100 Subject: [PATCH] USD to SDF: Initial commit (#827) Signed-off-by: ahcorde Co-authored-by: Ashton Larkin <42042756+adlarkin@users.noreply.github.com> --- test/usd/materials/textures/FANS_Albedo.png | 0 .../usd/materials/textures/FANS_Metalness.png | 0 test/usd/materials/textures/FANS_Normal.png | 0 .../usd/materials/textures/FANS_Roughness.png | 0 test/usd/upAxisY.usda | 204 +++++ test/usd/upAxisZ.usda | 721 ++++++++++++++++++ test/usd/upAxis_wrong.usda | 5 + usd/include/sdf/usd/UsdError.hh | 12 + usd/include/sdf/usd/usd_parser/USDData.hh | 118 +++ usd/include/sdf/usd/usd_parser/USDStage.hh | 69 ++ usd/src/CMakeLists.txt | 7 +- usd/src/usd_parser/USDData.cc | 309 ++++++++ usd/src/usd_parser/USDData_TEST.cc | 123 +++ usd/src/usd_parser/USDMaterial.cc | 325 ++++++++ usd/src/usd_parser/USDMaterial.hh | 54 ++ usd/src/usd_parser/USDStage.cc | 139 ++++ usd/src/usd_parser/USDStage_TEST.cc | 71 ++ 17 files changed, 2155 insertions(+), 2 deletions(-) create mode 100644 test/usd/materials/textures/FANS_Albedo.png create mode 100644 test/usd/materials/textures/FANS_Metalness.png create mode 100644 test/usd/materials/textures/FANS_Normal.png create mode 100644 test/usd/materials/textures/FANS_Roughness.png create mode 100644 test/usd/upAxisY.usda create mode 100644 test/usd/upAxisZ.usda create mode 100644 test/usd/upAxis_wrong.usda create mode 100644 usd/include/sdf/usd/usd_parser/USDData.hh create mode 100644 usd/include/sdf/usd/usd_parser/USDStage.hh create mode 100644 usd/src/usd_parser/USDData.cc create mode 100644 usd/src/usd_parser/USDData_TEST.cc create mode 100644 usd/src/usd_parser/USDMaterial.cc create mode 100644 usd/src/usd_parser/USDMaterial.hh create mode 100644 usd/src/usd_parser/USDStage.cc create mode 100644 usd/src/usd_parser/USDStage_TEST.cc diff --git a/test/usd/materials/textures/FANS_Albedo.png b/test/usd/materials/textures/FANS_Albedo.png new file mode 100644 index 000000000..e69de29bb diff --git a/test/usd/materials/textures/FANS_Metalness.png b/test/usd/materials/textures/FANS_Metalness.png new file mode 100644 index 000000000..e69de29bb diff --git a/test/usd/materials/textures/FANS_Normal.png b/test/usd/materials/textures/FANS_Normal.png new file mode 100644 index 000000000..e69de29bb diff --git a/test/usd/materials/textures/FANS_Roughness.png b/test/usd/materials/textures/FANS_Roughness.png new file mode 100644 index 000000000..e69de29bb diff --git a/test/usd/upAxisY.usda b/test/usd/upAxisY.usda new file mode 100644 index 000000000..90bece191 --- /dev/null +++ b/test/usd/upAxisY.usda @@ -0,0 +1,204 @@ +#usda 1.0 +( + metersPerUnit = 1.0 + upAxis = "Y" +) + +def "shapes" +{ + def PhysicsScene "physics" + { + vector3f physics:gravityDirection = (0, 0, -1) + float physics:gravityMagnitude = 9.8 + } + + def Xform "ground_plane" + { + float3 xformOp:rotateXYZ = (0, 0, 0) + double3 xformOp:translate = (0, 0, -0.125) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def Xform "link" + { + float3 xformOp:rotateXYZ = (0, 0, 0) + double3 xformOp:translate = (0, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def Xform "visual" + { + float3 xformOp:rotateXYZ = (0, 0, 0) + double3 xformOp:translate = (0, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def Cube "geometry" ( + prepend apiSchemas = ["PhysicsCollisionAPI"] + ) + { + float3[] extent = [(-0.5, -0.5, -0.5), (0.5, 0.5, 0.5)] + rel material:binding = + double size = 1 + float3 xformOp:scale = (100, 100, 0.25) + uniform token[] xformOpOrder = ["xformOp:scale"] + } + } + } + } + + def Xform "box" + { + float3 xformOp:rotateXYZ = (0, 0, 0) + double3 xformOp:translate = (0, 0, 0.5) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def Xform "box_link" ( + prepend apiSchemas = ["PhysicsRigidBodyAPI", "PhysicsMassAPI"] + ) + { + point3f physics:centerOfMass = (0, 0, 0) + float3 physics:diagonalInertia = (0.16666, 0.16666, 0.16666) + float physics:mass = 1 + float3 xformOp:rotateXYZ = (0, 0, 0) + double3 xformOp:translate = (0, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def Xform "box_visual" + { + float3 xformOp:rotateXYZ = (0, 0, 0) + double3 xformOp:translate = (0, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def Cube "geometry" ( + prepend apiSchemas = ["PhysicsCollisionAPI"] + ) + { + float3[] extent = [(-0.5, -0.5, -0.5), (0.5, 0.5, 0.5)] + rel material:binding = + double size = 1 + float3 xformOp:scale = (1, 1, 1) + uniform token[] xformOpOrder = ["xformOp:scale"] + } + } + } + } + + def Scope "Looks" + { + def Material "Material_0" + { + token outputs:mdl:displacement.connect = + token outputs:mdl:surface.connect = + token outputs:mdl:volume.connect = + + def Shader "Shader" + { + uniform token info:implementationSource = "sourceAsset" + uniform asset info:mdl:sourceAsset = @OmniPBR.mdl@ + uniform token info:mdl:sourceAsset:subIdentifier = "OmniPBR" + color3f inputs:diffuse_color_constant = (0.8, 0.8, 0.8) ( + customData = { + float3 default = (0.2, 0.2, 0.2) + dictionary range = { + float3 max = (100000, 100000, 100000) + float3 min = (0, 0, 0) + } + } + displayGroup = "Albedo" + displayName = "Base Color" + doc = "This is the base color" + ) + color3f inputs:emissive_color = (0, 0, 0) ( + customData = { + float3 default = (1, 0.1, 0.1) + dictionary range = { + float3 max = (100000, 100000, 100000) + float3 min = (0, 0, 0) + } + } + displayGroup = "Emissive" + displayName = "Emissive Color" + doc = "The emission color" + ) + float inputs:emissive_intensity = 1 ( + customData = { + int default = 40 + dictionary range = { + int max = 100000 + int min = 0 + } + } + displayGroup = "Emissive" + displayName = "Emissive Intensity" + doc = "Intensity of the emission" + ) + bool inputs:enable_emission = 1 ( + customData = { + int default = 0 + } + displayGroup = "Emissive" + displayName = "Enable Emissive" + doc = "Enables the emission of light from the material" + ) + token outputs:out + } + } + + def Material "Material_1" + { + token outputs:mdl:displacement.connect = + token outputs:mdl:surface.connect = + token outputs:mdl:volume.connect = + + def Shader "Shader" + { + uniform token info:implementationSource = "sourceAsset" + uniform asset info:mdl:sourceAsset = @OmniPBR.mdl@ + uniform token info:mdl:sourceAsset:subIdentifier = "OmniPBR" + color3f inputs:diffuse_color_constant = (1, 0, 0) ( + customData = { + float3 default = (0.2, 0.2, 0.2) + dictionary range = { + float3 max = (100000, 100000, 100000) + float3 min = (0, 0, 0) + } + } + displayGroup = "Albedo" + displayName = "Base Color" + doc = "This is the base color" + ) + color3f inputs:emissive_color = (0, 0, 0) ( + customData = { + float3 default = (1, 0.1, 0.1) + dictionary range = { + float3 max = (100000, 100000, 100000) + float3 min = (0, 0, 0) + } + } + displayGroup = "Emissive" + displayName = "Emissive Color" + doc = "The emission color" + ) + float inputs:emissive_intensity = 1 ( + customData = { + int default = 40 + dictionary range = { + int max = 100000 + int min = 0 + } + } + displayGroup = "Emissive" + displayName = "Emissive Intensity" + doc = "Intensity of the emission" + ) + bool inputs:enable_emission = 1 ( + customData = { + int default = 0 + } + displayGroup = "Emissive" + displayName = "Enable Emissive" + doc = "Enables the emission of light from the material" + ) + token outputs:out + } + } + } +} diff --git a/test/usd/upAxisZ.usda b/test/usd/upAxisZ.usda new file mode 100644 index 000000000..6933533c8 --- /dev/null +++ b/test/usd/upAxisZ.usda @@ -0,0 +1,721 @@ +#usda 1.0 +( + metersPerUnit = 0.01 + upAxis = "Z" +) + + +def "shapes" +{ + def PhysicsScene "physics" + { + vector3f physics:gravityDirection = (0, 0, -1) + float physics:gravityMagnitude = 9.8 + } + + def Xform "ground_plane" + { + float3 xformOp:rotateXYZ = (0, 0, 0) + double3 xformOp:translate = (0, 0, -0.125) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def Xform "link" + { + float3 xformOp:rotateXYZ = (0, 0, 0) + double3 xformOp:translate = (0, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def Xform "visual" + { + float3 xformOp:rotateXYZ = (0, 0, 0) + double3 xformOp:translate = (0, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def Cube "geometry" ( + prepend apiSchemas = ["PhysicsCollisionAPI"] + ) + { + float3[] extent = [(-0.5, -0.5, -0.5), (0.5, 0.5, 0.5)] + rel material:binding = + double size = 1 + float3 xformOp:scale = (100, 100, 0.25) + uniform token[] xformOpOrder = ["xformOp:scale"] + } + } + } + } + + def Xform "box" + { + float3 xformOp:rotateXYZ = (0, 0, 0) + double3 xformOp:translate = (0, 0, 0.5) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def Xform "box_link" ( + prepend apiSchemas = ["PhysicsRigidBodyAPI", "PhysicsMassAPI"] + ) + { + point3f physics:centerOfMass = (0, 0, 0) + float3 physics:diagonalInertia = (0.16666, 0.16666, 0.16666) + float physics:mass = 1 + float3 xformOp:rotateXYZ = (0, 0, 0) + double3 xformOp:translate = (0, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def Xform "box_visual" + { + float3 xformOp:rotateXYZ = (0, 0, 0) + double3 xformOp:translate = (0, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def Cube "geometry" ( + prepend apiSchemas = ["PhysicsCollisionAPI"] + ) + { + float3[] extent = [(-0.5, -0.5, -0.5), (0.5, 0.5, 0.5)] + rel material:binding = + double size = 1 + float3 xformOp:scale = (1, 1, 1) + uniform token[] xformOpOrder = ["xformOp:scale"] + } + } + } + } + + def Xform "cylinder" + { + float3 xformOp:rotateXYZ = (0, 0, 0) + double3 xformOp:translate = (0, -1.5, 0.5) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def Xform "cylinder_link" ( + prepend apiSchemas = ["PhysicsRigidBodyAPI", "PhysicsMassAPI"] + ) + { + point3f physics:centerOfMass = (0, 0, 0) + float3 physics:diagonalInertia = (0.1458, 0.1458, 0.125) + float physics:mass = 1 + float3 xformOp:rotateXYZ = (0, 0, 0) + double3 xformOp:translate = (0, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def Xform "cylinder_visual" + { + float3 xformOp:rotateXYZ = (0, 0, 0) + double3 xformOp:translate = (0, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def Cylinder "geometry" ( + prepend apiSchemas = ["PhysicsCollisionAPI"] + ) + { + float3[] extent = [(-0.5, -0.5, -0.5), (0.5, 0.5, 0.5)] + double height = 1 + rel material:binding = + double radius = 0.5 + } + } + } + } + + def Xform "sphere" + { + float3 xformOp:rotateXYZ = (0, 0, 0) + double3 xformOp:translate = (0, 1.5, 0.5) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def Xform "sphere_link" ( + prepend apiSchemas = ["PhysicsRigidBodyAPI", "PhysicsMassAPI"] + ) + { + point3f physics:centerOfMass = (0, 0, 0) + float3 physics:diagonalInertia = (0.1, 0.1, 0.1) + float physics:mass = 1 + float3 xformOp:rotateXYZ = (0, 0, 0) + double3 xformOp:translate = (0, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def Xform "sphere_visual" + { + float3 xformOp:rotateXYZ = (0, 0, 0) + double3 xformOp:translate = (0, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def Sphere "geometry" ( + prepend apiSchemas = ["PhysicsCollisionAPI"] + ) + { + float3[] extent = [(-0.5, -0.5, -0.5), (0.5, 0.5, 0.5)] + rel material:binding = + double radius = 0.5 + } + } + } + } + + def Xform "capsule" + { + float3 xformOp:rotateXYZ = (0, 0, 0) + double3 xformOp:translate = (0, -3, 0.5) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def Xform "capsule_link" ( + prepend apiSchemas = ["PhysicsRigidBodyAPI", "PhysicsMassAPI"] + ) + { + point3f physics:centerOfMass = (0, 0, 0) + float3 physics:diagonalInertia = (0.074154, 0.074154, 0.018769) + float physics:mass = 1 + float3 xformOp:rotateXYZ = (0, 0, 0) + double3 xformOp:translate = (0, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def Xform "capsule_visual" + { + float3 xformOp:rotateXYZ = (0, 0, 0) + double3 xformOp:translate = (0, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def Capsule "geometry" ( + prepend apiSchemas = ["PhysicsCollisionAPI"] + ) + { + float3[] extent = [(-0.2, -0.2, -0.5), (0.2, 0.2, 0.5)] + double height = 0.6 + rel material:binding = + double radius = 0.2 + } + } + } + } + + def Xform "ellipsoid" + { + float3 xformOp:rotateXYZ = (0, 0, 0) + double3 xformOp:translate = (0, 3, 0.5) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def Xform "ellipsoid_link" ( + prepend apiSchemas = ["PhysicsRigidBodyAPI", "PhysicsMassAPI"] + ) + { + point3f physics:centerOfMass = (0, 0, 0) + float3 physics:diagonalInertia = (0.068, 0.058, 0.026) + float physics:mass = 1 + float3 xformOp:rotateXYZ = (0, 0, 0) + double3 xformOp:translate = (0, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def Xform "ellipsoid_visual" + { + float3 xformOp:rotateXYZ = (0, 0, 0) + double3 xformOp:translate = (0, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def Sphere "geometry" ( + prepend apiSchemas = ["PhysicsCollisionAPI"] + ) + { + float3[] extent = [(-0.5, -0.5, -0.5), (0.5, 0.5, 0.5)] + rel material:binding = + double radius = 0.5 + float3 xformOp:scale = (0.4, 0.6, 1) + uniform token[] xformOpOrder = ["xformOp:scale"] + } + } + } + } + + def DistantLight "sun" + { + float inputs:intensity = 1000 + float intensity = 100 + float3 xformOp:rotateXYZ = (0, 0, 0) + double3 xformOp:translate = (0, 0, 10) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + } +} + +def Scope "Looks" +{ + def Material "Material_0" + { + token outputs:mdl:displacement.connect = + token outputs:mdl:surface.connect = + token outputs:mdl:volume.connect = + + def Shader "Shader" + { + uniform token info:implementationSource = "sourceAsset" + uniform asset info:mdl:sourceAsset = @OmniPBR.mdl@ + uniform token info:mdl:sourceAsset:subIdentifier = "OmniPBR" + color3f inputs:diffuse_color_constant = (0.8, 0.8, 0.8) ( + customData = { + float3 default = (0.2, 0.2, 0.2) + dictionary range = { + float3 max = (100000, 100000, 100000) + float3 min = (0, 0, 0) + } + } + displayGroup = "Albedo" + displayName = "Base Color" + doc = "This is the base color" + ) + color3f inputs:emissive_color = (0, 0, 0) ( + customData = { + float3 default = (1, 0.1, 0.1) + dictionary range = { + float3 max = (100000, 100000, 100000) + float3 min = (0, 0, 0) + } + } + displayGroup = "Emissive" + displayName = "Emissive Color" + doc = "The emission color" + ) + float inputs:emissive_intensity = 1 ( + customData = { + int default = 40 + dictionary range = { + int max = 100000 + int min = 0 + } + } + displayGroup = "Emissive" + displayName = "Emissive Intensity" + doc = "Intensity of the emission" + ) + bool inputs:enable_emission = 1 ( + customData = { + int default = 0 + } + displayGroup = "Emissive" + displayName = "Enable Emissive" + doc = "Enables the emission of light from the material" + ) + token outputs:out + } + } + + def Material "Material_1" + { + token outputs:mdl:displacement.connect = + token outputs:mdl:surface.connect = + token outputs:mdl:volume.connect = + + def Shader "Shader" + { + uniform token info:implementationSource = "sourceAsset" + uniform asset info:mdl:sourceAsset = @OmniPBR.mdl@ + uniform token info:mdl:sourceAsset:subIdentifier = "OmniPBR" + color3f inputs:diffuse_color_constant = (1, 0, 0) ( + customData = { + float3 default = (0.2, 0.2, 0.2) + dictionary range = { + float3 max = (100000, 100000, 100000) + float3 min = (0, 0, 0) + } + } + displayGroup = "Albedo" + displayName = "Base Color" + doc = "This is the base color" + ) + color3f inputs:emissive_color = (0, 0, 0) ( + customData = { + float3 default = (1, 0.1, 0.1) + dictionary range = { + float3 max = (100000, 100000, 100000) + float3 min = (0, 0, 0) + } + } + displayGroup = "Emissive" + displayName = "Emissive Color" + doc = "The emission color" + ) + float inputs:emissive_intensity = 1 ( + customData = { + int default = 40 + dictionary range = { + int max = 100000 + int min = 0 + } + } + displayGroup = "Emissive" + displayName = "Emissive Intensity" + doc = "Intensity of the emission" + ) + bool inputs:enable_emission = 1 ( + customData = { + int default = 0 + } + displayGroup = "Emissive" + displayName = "Enable Emissive" + doc = "Enables the emission of light from the material" + ) + token outputs:out + } + } + + def Material "Material_2" + { + token outputs:mdl:displacement.connect = + token outputs:mdl:surface.connect = + token outputs:mdl:volume.connect = + + def Shader "Shader" + { + uniform token info:implementationSource = "sourceAsset" + uniform asset info:mdl:sourceAsset = @OmniPBR.mdl@ + uniform token info:mdl:sourceAsset:subIdentifier = "OmniPBR" + color3f inputs:diffuse_color_constant = (0, 1, 0) ( + customData = { + float3 default = (0.2, 0.2, 0.2) + dictionary range = { + float3 max = (100000, 100000, 100000) + float3 min = (0, 0, 0) + } + } + displayGroup = "Albedo" + displayName = "Base Color" + doc = "This is the base color" + ) + color3f inputs:emissive_color = (0, 0, 0) ( + customData = { + float3 default = (1, 0.1, 0.1) + dictionary range = { + float3 max = (100000, 100000, 100000) + float3 min = (0, 0, 0) + } + } + displayGroup = "Emissive" + displayName = "Emissive Color" + doc = "The emission color" + ) + float inputs:emissive_intensity = 1 ( + customData = { + int default = 40 + dictionary range = { + int max = 100000 + int min = 0 + } + } + displayGroup = "Emissive" + displayName = "Emissive Intensity" + doc = "Intensity of the emission" + ) + bool inputs:enable_emission = 1 ( + customData = { + int default = 0 + } + displayGroup = "Emissive" + displayName = "Enable Emissive" + doc = "Enables the emission of light from the material" + ) + token outputs:out + } + } + + def Material "Material_3" + { + token outputs:mdl:displacement.connect = + token outputs:mdl:surface.connect = + token outputs:mdl:volume.connect = + + def Shader "Shader" + { + uniform token info:implementationSource = "sourceAsset" + uniform asset info:mdl:sourceAsset = @OmniPBR.mdl@ + uniform token info:mdl:sourceAsset:subIdentifier = "OmniPBR" + color3f inputs:diffuse_color_constant = (0, 0, 1) ( + customData = { + float3 default = (0.2, 0.2, 0.2) + dictionary range = { + float3 max = (100000, 100000, 100000) + float3 min = (0, 0, 0) + } + } + displayGroup = "Albedo" + displayName = "Base Color" + doc = "This is the base color" + ) + color3f inputs:emissive_color = (0, 0, 0) ( + customData = { + float3 default = (1, 0.1, 0.1) + dictionary range = { + float3 max = (100000, 100000, 100000) + float3 min = (0, 0, 0) + } + } + displayGroup = "Emissive" + displayName = "Emissive Color" + doc = "The emission color" + ) + float inputs:emissive_intensity = 1 ( + customData = { + int default = 40 + dictionary range = { + int max = 100000 + int min = 0 + } + } + displayGroup = "Emissive" + displayName = "Emissive Intensity" + doc = "Intensity of the emission" + ) + bool inputs:enable_emission = 1 ( + customData = { + int default = 0 + } + displayGroup = "Emissive" + displayName = "Enable Emissive" + doc = "Enables the emission of light from the material" + ) + token outputs:out + } + } + + def Material "Material_4" + { + token outputs:mdl:displacement.connect = + token outputs:mdl:surface.connect = + token outputs:mdl:volume.connect = + + def Shader "Shader" + { + uniform token info:implementationSource = "sourceAsset" + uniform asset info:mdl:sourceAsset = @OmniPBR.mdl@ + uniform token info:mdl:sourceAsset:subIdentifier = "OmniPBR" + color3f inputs:diffuse_color_constant = (1, 1, 0) ( + customData = { + float3 default = (0.2, 0.2, 0.2) + dictionary range = { + float3 max = (100000, 100000, 100000) + float3 min = (0, 0, 0) + } + } + displayGroup = "Albedo" + displayName = "Base Color" + doc = "This is the base color" + ) + color3f inputs:emissive_color = (0, 0, 0) ( + customData = { + float3 default = (1, 0.1, 0.1) + dictionary range = { + float3 max = (100000, 100000, 100000) + float3 min = (0, 0, 0) + } + } + displayGroup = "Emissive" + displayName = "Emissive Color" + doc = "The emission color" + ) + float inputs:emissive_intensity = 1 ( + customData = { + int default = 40 + dictionary range = { + int max = 100000 + int min = 0 + } + } + displayGroup = "Emissive" + displayName = "Emissive Intensity" + doc = "Intensity of the emission" + ) + bool inputs:enable_emission = 1 ( + customData = { + int default = 0 + } + displayGroup = "Emissive" + displayName = "Enable Emissive" + doc = "Enables the emission of light from the material" + ) + token outputs:out + } + } + + def Material "Material_5" + { + token outputs:mdl:displacement.connect = + token outputs:mdl:surface.connect = + token outputs:mdl:volume.connect = + + def Shader "Shader" + { + uniform token info:implementationSource = "sourceAsset" + uniform asset info:mdl:sourceAsset = @OmniPBR.mdl@ + uniform token info:mdl:sourceAsset:subIdentifier = "OmniPBR" + color3f inputs:diffuse_color_constant = (1, 0, 1) ( + customData = { + float3 default = (0.2, 0.2, 0.2) + dictionary range = { + float3 max = (100000, 100000, 100000) + float3 min = (0, 0, 0) + } + } + displayGroup = "Albedo" + displayName = "Base Color" + doc = "This is the base color" + ) + color3f inputs:emissive_color = (0, 0, 0) ( + customData = { + float3 default = (1, 0.1, 0.1) + dictionary range = { + float3 max = (100000, 100000, 100000) + float3 min = (0, 0, 0) + } + } + displayGroup = "Emissive" + displayName = "Emissive Color" + doc = "The emission color" + ) + float inputs:emissive_intensity = 1 ( + customData = { + int default = 40 + dictionary range = { + int max = 100000 + int min = 0 + } + } + displayGroup = "Emissive" + displayName = "Emissive Intensity" + doc = "Intensity of the emission" + ) + bool inputs:enable_emission = 1 ( + customData = { + int default = 0 + } + displayGroup = "Emissive" + displayName = "Enable Emissive" + doc = "Enables the emission of light from the material" + ) + token outputs:out + } + } + + def Material "Material_textures" + { + token outputs:mdl:displacement.connect = + token outputs:mdl:surface.connect = + token outputs:mdl:volume.connect = + + def Shader "Shader" + { + uniform token info:implementationSource = "sourceAsset" + uniform asset info:mdl:sourceAsset = @OmniPBR.mdl@ + uniform token info:mdl:sourceAsset:subIdentifier = "OmniPBR" + color3f inputs:diffuse_color_constant = (1, 1, 1) ( + customData = { + float3 default = (0.2, 0.2, 0.2) + dictionary range = { + float3 max = (100000, 100000, 100000) + float3 min = (0, 0, 0) + } + } + displayGroup = "Albedo" + displayName = "Base Color" + doc = "This is the base color" + ) + asset inputs:diffuse_texture = @materials/textures/FANS_Albedo.png@ ( + colorSpace = "auto" + customData = { + asset default = @@ + } + displayGroup = "Albedo" + displayName = "Base Map" + ) + color3f inputs:emissive_color = (0, 0, 0) ( + customData = { + float3 default = (1, 0.1, 0.1) + dictionary range = { + float3 max = (100000, 100000, 100000) + float3 min = (0, 0, 0) + } + } + displayGroup = "Emissive" + displayName = "Emissive Color" + doc = "The emission color" + ) + float inputs:emissive_intensity = 1 ( + customData = { + int default = 40 + dictionary range = { + int max = 100000 + int min = 0 + } + } + displayGroup = "Emissive" + displayName = "Emissive Intensity" + doc = "Intensity of the emission" + ) + bool inputs:enable_emission = 1 ( + customData = { + int default = 0 + } + displayGroup = "Emissive" + displayName = "Enable Emissive" + doc = "Enables the emission of light from the material" + ) + float inputs:metallic_constant = 0.5 ( + customData = { + double default = 0.5 + dictionary range = { + int max = 1 + int min = 0 + } + } + displayGroup = "Reflectivity" + displayName = "Metallic Amount" + doc = "Metallic Material" + ) + asset inputs:metallic_texture = @materials/textures/FANS_Metalness.png@ ( + colorSpace = "raw" + customData = { + asset default = @@ + } + displayGroup = "Reflectivity" + displayName = "Metallic Map" + ) + asset inputs:normalmap_texture = @materials/textures/FANS_Normal.png@ ( + colorSpace = "raw" + customData = { + asset default = @@ + } + displayGroup = "Normal" + displayName = "Normal Map" + ) + float inputs:reflection_roughness_constant = 0.5 ( + customData = { + double default = 0.5 + dictionary range = { + int max = 1 + int min = 0 + } + } + displayGroup = "Reflectivity" + displayName = "Roughness Amount" + doc = "Higher roughness values lead to more blurry reflections" + ) + bool inputs:reflection_roughness_texture_influence = 1 ( + colorSpace = "raw" + customData = { + int default = 0 + dictionary range = { + int max = 1 + int min = 0 + } + } + displayGroup = "Reflectivity" + displayName = "Roughness Map Influence" + ) + asset inputs:reflectionroughness_texture = @materials/textures/FANS_Roughness.png@ ( + colorSpace = "raw" + customData = { + asset default = @@ + } + displayGroup = "RoughnessMap" + displayName = "RoughnessMap Map" + ) + token outputs:out + } + } +} diff --git a/test/usd/upAxis_wrong.usda b/test/usd/upAxis_wrong.usda new file mode 100644 index 000000000..f35d392d9 --- /dev/null +++ b/test/usd/upAxis_wrong.usda @@ -0,0 +1,5 @@ +#usda 1.0 +( + metersPerUnit = 0.009999999776482582 + upAxis = "X" +) diff --git a/usd/include/sdf/usd/UsdError.hh b/usd/include/sdf/usd/UsdError.hh index 28742e2d6..b83c8ae5f 100644 --- a/usd/include/sdf/usd/UsdError.hh +++ b/usd/include/sdf/usd/UsdError.hh @@ -69,6 +69,18 @@ namespace sdf /// \brief Invalid material INVALID_MATERIAL, + + /// \brief Invalid usd file + INVALID_USD_FILE, + + /// \brief Invalid up axis + INVALID_UP_AXIS, + + /// \brief Prim is missing a particular attribute + PRIM_MISSING_ATTRIBUTE, + + /// \brief Prim is of the incorrect schema type + PRIM_INCORRECT_SCHEMA_TYPE, }; class IGNITION_SDFORMAT_USD_VISIBLE UsdError diff --git a/usd/include/sdf/usd/usd_parser/USDData.hh b/usd/include/sdf/usd/usd_parser/USDData.hh new file mode 100644 index 000000000..f9d25edce --- /dev/null +++ b/usd/include/sdf/usd/usd_parser/USDData.hh @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * 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 + * + * http://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. + * +*/ + +#ifndef SDF_USD_USD_PARSER_USDDATA_HH_ +#define SDF_USD_USD_PARSER_USDDATA_HH_ + +#include +#include +#include +#include +#include + +#include + +#include "sdf/Material.hh" +#include "sdf/Types.hh" +#include "sdf/sdf_config.h" +#include "sdf/system_util.hh" +#include "sdf/usd/usd_parser/USDStage.hh" +#include "sdf/usd/Export.hh" +#include "sdf/usd/UsdError.hh" + +namespace sdf +{ + // Inline bracket to help doxygen filtering. + inline namespace SDF_VERSION_NAMESPACE { + // + namespace usd + { + /// \brief this class will handle the data of a stage + /// It will parse the materials in the stage + /// If the stage has some references to other stages, this class + /// will read them and make this data available here too. + class IGNITION_SDFORMAT_USD_VISIBLE USDData + { + /// \brief Constructor + public: explicit USDData(const std::string &_filename); + + /// \brief Initialize the data inside the class with the stage + /// defined in the constructor + /// \return Errors, which is a vector of Error objects. Each Error includes + /// an error code and message. An empty vector indicates no error. + public: UsdErrors Init(); + + /// \brief If a stage contains substages, this will allow to include + /// them. + /// \return Errors, which is a vector of Error objects. Each Error includes + /// an error code and message. An empty vector indicates no error. + public: UsdErrors AddStage(const std::string &_ref); + + /// \brief Read materials + /// \return Errors, which is a vector of Error objects. Each Error includes + /// an error code and message. An empty vector indicates no error. + public: UsdErrors ParseMaterials(); + + /// \brief Get all materials readed in the stage + public: const std::unordered_map & + Materials() const; + + /// \brief Get all sdformat models inside the stage + public: const std::set &Models() const; + + /// \brief Get all stages (the main one and all the referenced). + public: const std::unordered_map> & + AllReferences() const; + + /// \brief Find a path and get the data + /// \param[in] _name Name of the path to find + /// \return A pair with the name of the stage and the data + public: const std::pair> + FindStage(const std::string &_name); + + public: friend std::ostream& operator<<( + std::ostream& os, const USDData& data) + { + os << "References:" << "\n"; + for (auto &ref : data.AllReferences()) + { + os << "\t" << ref.first << "\n"; + os << "\t\t" << ref.second->UpAxis() << "\n"; + os << "\t\t" << ref.second->MetersPerUnit() << "\n"; + for (auto &path : ref.second->USDPaths()) + { + os << "\t\tPath " << path << '\n'; + } + } + os << "Models:" << "\n"; + auto models = data.Models(); + for (const auto &model : models) + { + os << "\t" << model << "\n"; + } + + return os; + } + + /// \brief Private data pointer. + IGN_UTILS_IMPL_PTR(dataPtr) + }; + } +} +} + +#endif diff --git a/usd/include/sdf/usd/usd_parser/USDStage.hh b/usd/include/sdf/usd/usd_parser/USDStage.hh new file mode 100644 index 000000000..604b69f75 --- /dev/null +++ b/usd/include/sdf/usd/usd_parser/USDStage.hh @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * 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 + * + * http://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. + * +*/ + +#ifndef SDF_USD_USD_PARSER_USDSTAGE_HH_ +#define SDF_USD_USD_PARSER_USDSTAGE_HH_ + +#include +#include + +#include + +#include "sdf/Types.hh" +#include "sdf/sdf_config.h" +#include "sdf/system_util.hh" +#include "sdf/usd/Export.hh" +#include "sdf/usd/UsdError.hh" + +namespace sdf +{ + // Inline bracket to help doxygen filtering. + inline namespace SDF_VERSION_NAMESPACE { + // + namespace usd + { + /// \brief this class will keep the data of a stage + /// - UpAxis + /// - MetersPerUnit + /// - All USD paths + class IGNITION_SDFORMAT_USD_VISIBLE USDStage + { + /// \brief Default constructor + /// \param[in] _refFileName File name of the stage in the disk + public: explicit USDStage(const std::string &_refFileName); + + /// \brief Initialize the data structure + /// \return Errors, which is a vector of Error objects. Each Error includes + /// an error code and message. An empty vector indicates no error. + public: UsdErrors Init(); + + /// \brief Get stage up axis + public: const std::string &UpAxis() const; + + /// \brief Get stage meter per unit + public: double MetersPerUnit() const; + + /// \brief Get USD paths available in the stage + public: const std::set &USDPaths() const; + + /// \brief Private data pointer. + IGN_UTILS_IMPL_PTR(dataPtr) + }; + } + } +} +#endif // SDF_USD_PARSER_USDSTAGE_HH diff --git a/usd/src/CMakeLists.txt b/usd/src/CMakeLists.txt index 29b4ef4b5..0531a17a9 100644 --- a/usd/src/CMakeLists.txt +++ b/usd/src/CMakeLists.txt @@ -10,6 +10,9 @@ set(sources sdf_parser/Sensor.cc sdf_parser/Visual.cc sdf_parser/World.cc + usd_parser/USDData.cc + usd_parser/USDMaterial.cc + usd_parser/USDStage.cc ) ign_add_component(usd SOURCES ${sources} GET_TARGET_NAME usd_target) @@ -33,11 +36,11 @@ set(gtest_sources sdf_parser/Light_Sdf2Usd_TEST.cc sdf_parser/Link_Sdf2Usd_TEST.cc sdf_parser/Material_Sdf2Usd_TEST.cc - # TODO(adlarkin) add a test for SDF -> USD models once model parsing - # functionality is complete sdf_parser/Sensors_Sdf2Usd_TEST.cc sdf_parser/Visual_Sdf2Usd_TEST.cc sdf_parser/World_Sdf2Usd_TEST.cc + usd_parser/USDData_TEST.cc + usd_parser/USDStage_TEST.cc Conversions_TEST.cc UsdError_TEST.cc UsdUtils_TEST.cc diff --git a/usd/src/usd_parser/USDData.cc b/usd/src/usd_parser/USDData.cc new file mode 100644 index 000000000..709603687 --- /dev/null +++ b/usd/src/usd_parser/USDData.cc @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * 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 + * + * http://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. + * +*/ +#include "sdf/usd/usd_parser/USDData.hh" + +#include +#include +#include + +// TODO(ahcorde) this is to remove deprecated "warnings" in usd, these warnings +// are reported using #pragma message so normal diagnostic flags cannot remove +// them. This workaround requires this block to be used whenever usd is +// included. +#pragma push_macro ("__DEPRECATED") +#undef __DEPRECATED +#include +#include +#include +#include +#pragma pop_macro ("__DEPRECATED") + +#include +#include + +#include "sdf/Material.hh" +#include "USDMaterial.hh" + +namespace sdf { +// Inline bracket to help doxygen filtering. +inline namespace SDF_VERSION_NAMESPACE { +// +namespace usd { + /// \brief USDStage private data. + class USDData::Implementation + { + public: + /// File name of the main stage + std::string filename; + + /// Directory where the main stage is located + std::string directoryPath; + + /// map with the filename and its own Stage data + std::unordered_map> references; + + /// Models + std::set models; + + /// Materials availables in the stage and substages + std::unordered_map materials; + + /// Add all subdirectories that are inside the stage folder + /// This will help us to find other stages and/or textures. + /// \param[in] _path Path of the subdirectory to add + void AddSubdirectories(const std::string &_path) + { + for (ignition::common::DirIter file(_path); + file != ignition::common::DirIter(); ++file) + { + std::string current(*file); + + if (ignition::common::isDirectory(current)) + { + auto systemPaths = ignition::common::systemPaths(); + systemPaths->AddFilePaths(current); + this->AddSubdirectories(current); + } + } + } + }; + + ///////////////////////////////////////////////// + USDData::USDData(const std::string &_filename) + : dataPtr(ignition::utils::MakeImpl()) + { + this->dataPtr->filename = _filename; + } + + ///////////////////////////////////////////////// + const std::unordered_map & + USDData::Materials() const + { + return this->dataPtr->materials; + } + + ///////////////////////////////////////////////// + const std::set &USDData::Models() const + { + return this->dataPtr->models; + } + + ///////////////////////////////////////////////// + const std::unordered_map> & + USDData::AllReferences() const + { + return this->dataPtr->references; + } + + ///////////////////////////////////////////////// + UsdErrors USDData::Init() + { + UsdErrors errors; + + auto usdStage = std::make_shared(this->dataPtr->filename); + UsdErrors errorsInit = usdStage->Init(); + if(!errorsInit.empty()) + { + errors.insert(errors.end(), errorsInit.begin(), errorsInit.end()); + return errors; + } + + // Add the base stage to the structure + this->dataPtr->references.insert( + {this->dataPtr->filename, + usdStage + }); + + // it's the stage available + auto referencee = pxr::UsdStage::Open(this->dataPtr->filename); + if (!referencee) + { + errors.emplace_back(UsdError( + sdf::usd::UsdErrorCode::INVALID_USD_FILE, + "Failed to load usd file")); + return errors; + } + + this->dataPtr->directoryPath = ignition::common::absPath( + this->dataPtr->filename); + + std::string basename = ignition::common::basename( + this->dataPtr->directoryPath); + this->dataPtr->directoryPath = ignition::common::replaceAll( + this->dataPtr->directoryPath, basename, ""); + + this->dataPtr->AddSubdirectories(this->dataPtr->directoryPath); + + auto range = pxr::UsdPrimRange::Stage(referencee); + + for (auto const &_prim : range) + { + if (_prim.IsA()) + { + continue; + } + + if (_prim.IsA()) + { + continue; + } + + std::string primName = pxr::TfStringify(_prim.GetPath()); + std::vector tokens = ignition::common::split(primName, "/"); + if (tokens.size() == 1) + { + this->dataPtr->models.insert(tokens[0]); + } + + pxr::UsdPrimCompositionQuery query = + pxr::UsdPrimCompositionQuery::GetDirectReferences(_prim); + + std::vector arcs = + query.GetCompositionArcs(); + for (auto & a : arcs ) + { + pxr::SdfLayerHandle handler = a.GetIntroducingLayer(); + + for (auto & ref : handler->GetCompositionAssetDependencies()) + { + this->AddStage(ref); + } + } + } + + return errors; + } + + ///////////////////////////////////////////////// + UsdErrors USDData::ParseMaterials() + { + UsdErrors errors; + auto referencee = pxr::UsdStage::Open(this->dataPtr->filename); + if (!referencee) + { + errors.emplace_back(UsdError( + sdf::usd::UsdErrorCode::INVALID_USD_FILE, + "Failed to load usd file")); + return errors; + } + + auto range = pxr::UsdPrimRange::Stage(referencee); + + for (auto const &prim : range) + { + std::string primName = pxr::TfStringify(prim.GetPath()); + if (prim.IsA()) + { + std::string materialName = prim.GetName(); + + if (this->dataPtr->materials.find(materialName) + != this->dataPtr->materials.end()) + { + continue; + } + + sdf::Material material; + UsdErrors errrosMaterial = ParseMaterial(prim, material); + if (!errrosMaterial.empty()) + { + errors.emplace_back(UsdError( + sdf::usd::UsdErrorCode::SDF_TO_USD_PARSING_ERROR, + "Error parsing material")); + errors.insert( + errors.end(), errrosMaterial.begin(), errrosMaterial.end()); + return errors; + } + this->dataPtr->materials.insert(std::pair( + materialName, material)); + } + } + return errors; + } + + ///////////////////////////////////////////////// + const std::pair> + USDData::FindStage(const std::string &_name) + { + for (auto &ref : this->dataPtr->references) + { + if (ref.second != nullptr) + { + for (auto &path : ref.second->USDPaths()) + { + if (path == _name) + { + return ref; + } + } + } + } + return std::make_pair>("", nullptr); + } + + ///////////////////////////////////////////////// + UsdErrors USDData::AddStage(const std::string &_ref) + { + UsdErrors errors; + std::string key = _ref; + + auto search = this->dataPtr->references.find(_ref); + if (search == this->dataPtr->references.end()) + { + std::string basename = ignition::common::basename(key); + auto subDirectory = ignition::common::replaceAll(key, basename, ""); + + auto systemPaths = ignition::common::systemPaths(); + systemPaths->AddFilePaths(ignition::common::joinPaths( + this->dataPtr->directoryPath, subDirectory)); + + this->dataPtr->AddSubdirectories(ignition::common::joinPaths( + this->dataPtr->directoryPath, subDirectory)); + + std::string fileNameRef = ignition::common::findFile(basename); + if (fileNameRef.empty()) + { + errors.emplace_back(UsdError( + sdf::usd::UsdErrorCode::SDF_TO_USD_PARSING_ERROR, + "Not able to find asset [" + _ref + "]")); + return errors; + } + + auto usdStage = std::make_shared(fileNameRef); + UsdErrors errorsInit = usdStage->Init(); + if(!errorsInit.empty()) + { + errors.insert(errors.end(), errorsInit.begin(), errorsInit.end()); + return errors; + } + + this->dataPtr->references.insert( + {key, + std::make_shared(fileNameRef) + }); + return errors; + } + else + { + errors.emplace_back(UsdError( + sdf::usd::UsdErrorCode::SDF_TO_USD_PARSING_ERROR, + "Element already exists")); + return errors; + } + } +} +} +} diff --git a/usd/src/usd_parser/USDData_TEST.cc b/usd/src/usd_parser/USDData_TEST.cc new file mode 100644 index 000000000..a245b2075 --- /dev/null +++ b/usd/src/usd_parser/USDData_TEST.cc @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * 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 + * + * http://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. + * +*/ + +#include + +#include +#include + +#include + +#include +#include + +#include "test_config.h" +#include "test_utils.hh" + +#include +#include + +///////////////////////////////////////////////// +TEST(USDData, Constructor) +{ + // Open a invalid USD file + sdf::usd::USDData data(sdf::testing::TestFile("usd", "invalid_name")); + sdf::usd::UsdErrors errors = data.Init(); + EXPECT_EQ(1u, errors.size()); + + // Add test/usd directory to find some resources + auto systemPaths = ignition::common::systemPaths(); + systemPaths->AddFilePaths(sdf::testing::TestFile("usd")); + + // Open a valid USD file + { + sdf::testing::ScopeExit removeCopiedMaterials( + [] + { + ignition::common::removeAll( + ignition::common::joinPaths(ignition::common::cwd(), "materials")); + }); + + std::string filename = sdf::testing::TestFile("usd", "upAxisZ.usda"); + sdf::usd::USDData usdData(filename); + EXPECT_EQ(0u, usdData.Init().size()); + EXPECT_EQ(0u, usdData.ParseMaterials().size()); + + EXPECT_EQ(1u, usdData.AllReferences().size()); + EXPECT_EQ(2u, usdData.Models().size()); + EXPECT_EQ(7u, usdData.Materials().size()); + + auto materials = usdData.Materials(); + + auto material0 = materials["Material_0"]; + EXPECT_EQ(ignition::math::Color(0.8, 0.8, 0.8), material0.Diffuse()); + EXPECT_EQ(ignition::math::Color(0, 0, 0), material0.Emissive()); + + auto material1 = materials["Material_1"]; + EXPECT_EQ(ignition::math::Color(1, 0, 0), material1.Diffuse()); + EXPECT_EQ(ignition::math::Color(0, 0, 0), material1.Emissive()); + + auto material2 = materials["Material_2"]; + EXPECT_EQ(ignition::math::Color(0, 1, 0), material2.Diffuse()); + EXPECT_EQ(ignition::math::Color(0, 0, 0), material2.Emissive()); + + auto material3 = materials["Material_3"]; + EXPECT_EQ(ignition::math::Color(0, 0, 1), material3.Diffuse()); + EXPECT_EQ(ignition::math::Color(0, 0, 0), material3.Emissive()); + + auto material4 = materials["Material_4"]; + EXPECT_EQ(ignition::math::Color(1, 1, 0), material4.Diffuse()); + EXPECT_EQ(ignition::math::Color(0, 0, 0), material4.Emissive()); + + auto material5 = materials["Material_5"]; + EXPECT_EQ(ignition::math::Color(1, 0, 1), material5.Diffuse()); + EXPECT_EQ(ignition::math::Color(0, 0, 0), material5.Emissive()); + + auto materialTextures = materials["Material_textures"]; + const auto * pbr = materialTextures.PbrMaterial(); + ASSERT_TRUE(pbr); + const auto * workflow = pbr->Workflow(sdf::PbrWorkflowType::METAL); + ASSERT_TRUE(workflow); + EXPECT_EQ(ignition::math::Color(1, 1, 1), materialTextures.Diffuse()); + EXPECT_EQ( + "materials/textures/FANS_Albedo.png", + workflow->AlbedoMap()); + EXPECT_EQ(ignition::math::Color(0, 0, 0), materialTextures.Emissive()); + EXPECT_DOUBLE_EQ(0.5, workflow->Metalness()); + EXPECT_EQ( + "materials/textures/FANS_Metalness.png", + workflow->MetalnessMap()); + EXPECT_DOUBLE_EQ(0.5, workflow->Roughness()); + EXPECT_EQ( + "materials/textures/FANS_Roughness.png", + workflow->RoughnessMap()); + EXPECT_EQ( + "materials/textures/FANS_Normal.png", + workflow->NormalMap()); + + // Find a path inside the stage + auto boxStage = usdData.FindStage("box"); + EXPECT_EQ(filename, boxStage.first); + EXPECT_EQ("Z", boxStage.second->UpAxis()); + EXPECT_DOUBLE_EQ(0.01, boxStage.second->MetersPerUnit()); + + // Try to find a invalid path in the stage data + auto invalidStage = usdData.FindStage("invalid"); + EXPECT_EQ("", invalidStage.first); + EXPECT_EQ(nullptr, invalidStage.second); + } +} diff --git a/usd/src/usd_parser/USDMaterial.cc b/usd/src/usd_parser/USDMaterial.cc new file mode 100644 index 000000000..d7986be16 --- /dev/null +++ b/usd/src/usd_parser/USDMaterial.cc @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * 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 + * + * http://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. + * +*/ + +#include "USDMaterial.hh" + +#include +#include +#include +#include +#include + +#include "sdf/Pbr.hh" + +namespace sdf +{ + // Inline bracket to help doxygen filtering. + inline namespace SDF_VERSION_NAMESPACE { + // + namespace usd + { + ///////////////////////////////////////////////// + /// \brief Copy a file from one destination to another + /// \param[in] _ori The original file to copy + /// \param[in] _dest The destination for the copy of _ori + /// \return A list of UsdErrors. An empty list means no errors occurred when + /// copying _ori to _dest + UsdErrors copyFile(const std::string &_ori, const std::string &_dest) + { + UsdErrors errors; + if (ignition::common::exists(_ori)) + { + std::string baseName = ignition::common::basename(_dest); + std::string pathDest = ignition::common::replaceAll(_dest, baseName, ""); + ignition::common::createDirectories(pathDest); + if (!ignition::common::copyFile(_ori, _dest)) + { + errors.emplace_back( + Error(ErrorCode::FILE_READ, "Unable to copy the file [" + _ori + + "] to [" + _dest + "]")); + } + } + else + { + errors.emplace_back( + Error(ErrorCode::FILE_READ, "File [" + _ori + "] does not exist")); + } + return errors; + } + + /// \brief Helper method for getting an asset path + /// \param[in] _tokenName Name of the asset + /// \param[in] _shader Shader that holds the desired asset + /// \return The pxr::SdfAssetPath object that contains the asset identified by + /// _tokenName in _shader + pxr::SdfAssetPath assetPath(const pxr::TfToken &_tokenName, + const pxr::UsdShadeShader &_shader) + { + pxr::SdfAssetPath assetPath; + pxr::UsdShadeInput shadeInput = _shader.GetInput(_tokenName); + shadeInput.Get(&assetPath); + return assetPath; + } + + ///////////////////////////////////////////////// + UsdErrors ParseMaterial(const pxr::UsdPrim &_prim, sdf::Material &_material) + { + UsdErrors errors; + // if the prim is a Geom then get the color values + if (_prim.IsA()) + { + auto variantGeom = pxr::UsdGeomGprim(_prim); + + pxr::VtArray color(0, 0, 0); + variantGeom.GetDisplayColorAttr().Get(&color); + + pxr::VtFloatArray displayOpacity; + const std::string displayOpacityToken = "primvars:displayOpacity"; + auto opacityAttr = _prim.GetAttribute(pxr::TfToken(displayOpacityToken)); + if (!opacityAttr) + { + errors.push_back(UsdError(UsdErrorCode::PRIM_MISSING_ATTRIBUTE, + "Prim at path [" + _prim.GetPath().GetString() + + "] does not have an attribute with a pxr::TfToken of [" + + displayOpacityToken + "]")); + return errors; + } + opacityAttr.Get(&displayOpacity); + + double alpha = 1.0; + if (displayOpacity.size() > 0) + { + alpha = 1 - displayOpacity[0]; + } + // Refer to this comment in github to understand the ambient and diffuse + // https://github.com/osrf/sdformat/pull/526#discussion_r623937715 + _material.SetAmbient( + ignition::math::Color( + ignition::math::clamp(color[0][2] / 0.4, 0.0, 1.0), + ignition::math::clamp(color[0][1] / 0.4, 0.0, 1.0), + ignition::math::clamp(color[0][0] / 0.4, 0.0, 1.0), + alpha)); + _material.SetDiffuse( + ignition::math::Color( + ignition::math::clamp(color[0][2] / 0.8, 0.0, 1.0), + ignition::math::clamp(color[0][1] / 0.8, 0.0, 1.0), + ignition::math::clamp(color[0][0] / 0.8, 0.0, 1.0), + alpha)); + } + // if the prim is a shade Material then get the texture values + else if (_prim.IsA()) + { + auto variantMaterial = pxr::UsdShadeMaterial(_prim); + for (const auto &child : _prim.GetChildren()) + { + if (child.IsA()) + { + auto variantShader = pxr::UsdShadeShader(child); + + bool enableEmission = false; + bool isPBR = false; + sdf::PbrWorkflow pbrWorkflow; + ignition::math::Color emissiveColorCommon; + + for (const auto &input : variantShader.GetInputs()) + { + if (input.GetBaseName() == "diffuse_texture") + { + pxr::SdfAssetPath materialPath = + assetPath(pxr::TfToken("diffuse_texture"), variantShader); + pbrWorkflow.SetAlbedoMap(materialPath.GetAssetPath()); + std::string fullAlbedoName = + ignition::common::findFile(materialPath.GetAssetPath()); + UsdErrors errorCopy = copyFile( + fullAlbedoName, materialPath.GetAssetPath()); + if (!errorCopy.empty()) + { + errors.insert(errors.end(), errorCopy.begin(), errorCopy.end()); + return errors; + } + isPBR = true; + } + else if (input.GetBaseName() == "normalmap_texture") + { + pxr::SdfAssetPath materialPath = + assetPath(pxr::TfToken("normalmap_texture"), variantShader); + pbrWorkflow.SetNormalMap(materialPath.GetAssetPath()); + std::string fullNormalName = + ignition::common::findFile(materialPath.GetAssetPath()); + auto errorCopy = copyFile(fullNormalName, + materialPath.GetAssetPath()); + if (!errorCopy.empty()) + { + errors.insert(errors.end(), errorCopy.begin(), errorCopy.end()); + return errors; + } + isPBR = true; + } + else if (input.GetBaseName() == "reflectionroughness_texture") + { + pxr::SdfAssetPath materialPath = assetPath( + pxr::TfToken("reflectionroughness_texture"), variantShader); + pbrWorkflow.SetRoughnessMap(materialPath.GetAssetPath()); + std::string fullRoughnessName = + ignition::common::findFile(materialPath.GetAssetPath()); + auto errorCopy = copyFile( + fullRoughnessName, materialPath.GetAssetPath()); + if (!errorCopy.empty()) + { + errors.insert(errors.end(), errorCopy.begin(), errorCopy.end()); + return errors; + } + isPBR = true; + } + else if (input.GetBaseName() == "metallic_texture") + { + pxr::SdfAssetPath materialPath = assetPath( + pxr::TfToken("metallic_texture"), variantShader); + pbrWorkflow.SetMetalnessMap(materialPath.GetAssetPath()); + std::string fullMetalnessName = + ignition::common::findFile(materialPath.GetAssetPath()); + auto errorCopy = copyFile( + fullMetalnessName, materialPath.GetAssetPath()); + if (!errorCopy.empty()) + { + errors.insert(errors.end(), errorCopy.begin(), errorCopy.end()); + return errors; + } + isPBR = true; + } + else if (input.GetBaseName() == "diffuse_color_constant") + { + pxr::GfVec3f diffuseColor(0, 0, 0); + auto sourceInfoV = input.GetConnectedSources(); + if (sourceInfoV.size() > 0) + { + pxr::UsdShadeInput connectedInput = + sourceInfoV[0].source.GetInput(sourceInfoV[0].sourceName); + + const pxr::SdfPath& thisAttrPath = + connectedInput.GetAttr().GetPath(); + auto connectedPrim = + _prim.GetStage()->GetPrimAtPath(thisAttrPath.GetPrimPath()); + if (connectedPrim) + { + connectedPrim.GetAttribute( + pxr::TfToken("inputs:diffuse_color_constant")). + Get(&diffuseColor); + } + } + else + { + pxr::UsdShadeInput diffuseShaderInput = variantShader.GetInput( + pxr::TfToken("diffuse_color_constant")); + diffuseShaderInput.Get(&diffuseColor); + } + _material.SetDiffuse( + ignition::math::Color( + diffuseColor[0], + diffuseColor[1], + diffuseColor[2])); + } + else if (input.GetBaseName() == "metallic_constant") + { + pxr::UsdShadeInput metallicConstantShaderInput = + variantShader.GetInput(pxr::TfToken("metallic_constant")); + float metallicConstant; + metallicConstantShaderInput.Get(&metallicConstant); + pbrWorkflow.SetMetalness(metallicConstant); + isPBR = true; + } + else if (input.GetBaseName() == "reflection_roughness_constant") + { + auto sourceInfoV = input.GetConnectedSources(); + if (sourceInfoV.size() > 0) + { + pxr::UsdShadeInput connectedInput = + sourceInfoV[0].source.GetInput(sourceInfoV[0].sourceName); + + const pxr::SdfPath& thisAttrPath = + connectedInput.GetAttr().GetPath(); + auto connectedPrim = + _prim.GetStage()->GetPrimAtPath(thisAttrPath.GetPrimPath()); + if(connectedPrim) + { + float reflectionRoughnessConstant; + connectedPrim.GetAttribute( + pxr::TfToken("inputs:reflection_roughness_constant")). + Get(&reflectionRoughnessConstant); + pbrWorkflow.SetRoughness(reflectionRoughnessConstant); + isPBR = true; + } + } + else + { + pxr::UsdShadeInput reflectionRoughnessConstantShaderInput = + variantShader.GetInput( + pxr::TfToken("reflection_roughness_constant")); + float reflectionRoughnessConstant; + reflectionRoughnessConstantShaderInput. + Get(&reflectionRoughnessConstant); + pbrWorkflow.SetRoughness(reflectionRoughnessConstant); + isPBR = true; + } + } + else if (input.GetBaseName() == "enable_emission") + { + pxr::UsdShadeInput enableEmissiveShaderInput = + variantShader.GetInput(pxr::TfToken("enable_emission")); + enableEmissiveShaderInput.Get(&enableEmission); + } + else if (input.GetBaseName() == "emissive_color") + { + pxr::GfVec3f emissiveColor(0, 0, 0); + pxr::UsdShadeInput emissiveColorShaderInput = + variantShader.GetInput(pxr::TfToken("emissive_color")); + if (emissiveColorShaderInput.Get(&emissiveColor)) + { + emissiveColorCommon = ignition::math::Color( + emissiveColor[0], + emissiveColor[1], + emissiveColor[2]); + } + } + } + + if (enableEmission) + { + _material.SetEmissive(emissiveColorCommon); + } + + if (isPBR) + { + sdf::Pbr pbr; + pbr.SetWorkflow(sdf::PbrWorkflowType::METAL, pbrWorkflow); + _material.SetPbrMaterial(pbr); + } + } + } + } + else + { + errors.push_back(UsdError(UsdErrorCode::PRIM_INCORRECT_SCHEMA_TYPE, + "Prim at path [" + _prim.GetPath().GetString() + + "is not a pxr::UsdGeomGprim or a pxr::UsdShadeMaterial.")); + } + + return errors; + } +} +} +} diff --git a/usd/src/usd_parser/USDMaterial.hh b/usd/src/usd_parser/USDMaterial.hh new file mode 100644 index 000000000..694727147 --- /dev/null +++ b/usd/src/usd_parser/USDMaterial.hh @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * 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 + * + * http://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. + * +*/ + +#ifndef SDF_USD_USD_PARSER_UTILS_HH_ +#define SDF_USD_USD_PARSER_UTILS_HH_ + +// TODO(ahcorde) this is to remove deprecated "warnings" in usd, these warnings +// are reported using #pragma message so normal diagnostic flags cannot remove +// them. This workaround requires this block to be used whenever usd is +// included. +#pragma push_macro ("__DEPRECATED") +#undef __DEPRECATED +#include +#pragma pop_macro ("__DEPRECATED") + +#include "sdf/Material.hh" +#include "sdf/config.hh" +#include "sdf/usd/UsdError.hh" + +namespace sdf +{ + // Inline bracket to help doxygen filtering. + inline namespace SDF_VERSION_NAMESPACE { + // + namespace usd + { + /// brief Parse the material in a USD prim to a sdf::Material + /// If the prim is a pxr::UsdGeomGprim, get the color values. Otherwise, + /// if the prim is a pxr::UsdShadeMaterial, get the texture values + /// \param[in] _prim USD prim where the material is extracted + /// \param[out] _material The sdf::Material representation of _prim's + /// material + /// \return UsdErrors, which is a vector of UsdError objects. Each UsdError + /// includes an error code and message. An empty vector indicates no error. + UsdErrors ParseMaterial(const pxr::UsdPrim &_prim, + sdf::Material &_material); +} +} +} +#endif diff --git a/usd/src/usd_parser/USDStage.cc b/usd/src/usd_parser/USDStage.cc new file mode 100644 index 000000000..3f5e38d1f --- /dev/null +++ b/usd/src/usd_parser/USDStage.cc @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * 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 + * + * http://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. + * +*/ + +#include +#include +#include + +// TODO(ahcorde):this is to remove deprecated "warnings" in usd, these warnings +// are reported using #pragma message so normal diagnostic flags cannot remove +// them. This workaround requires this block to be used whenever usd is +// included. +#pragma push_macro ("__DEPRECATED") +#undef __DEPRECATED +#include +#include +#include +#include +#pragma pop_macro ("__DEPRECATED") + +#include "sdf/usd/usd_parser/USDStage.hh" + +namespace sdf { +// Inline bracke to help doxygen filtering. +inline namespace SDF_VERSION_NAMESPACE { +// +namespace usd +{ + /// \brief USDStage private data. + class USDStage::Implementation + { + public: + /// \brief Up Axis this must be "Z" or "Y". + std::string upAxis = "Z"; + + /// \brief Meter per unit + double metersPerUnit = 1.0; + + /// \brief All USD paths available in the file + std::set paths; + + std::string refFileName; + }; + + ///////////////////////////////////////////////// + USDStage::USDStage(const std::string &_refFileName) + : dataPtr(ignition::utils::MakeImpl()) + { + this->dataPtr->refFileName = _refFileName; + } + + ///////////////////////////////////////////////// + UsdErrors USDStage::Init() + { + UsdErrors errors; + + // Open the stage + auto referencee = pxr::UsdStage::Open(this->dataPtr->refFileName); + if (!referencee) + { + errors.emplace_back(UsdError( + sdf::usd::UsdErrorCode::INVALID_USD_FILE, + "Failed to load usd file")); + return errors; + } + + // Get meters per unit + this->dataPtr->metersPerUnit = + pxr::UsdGeomGetStageMetersPerUnit(referencee); + + // is it up axis define, if so, read the value, if the value is + // not 'Y' or 'Z' throw an exception + if (referencee->HasAuthoredMetadata(pxr::UsdGeomTokens->upAxis)) + { + pxr::TfToken axis; + referencee->GetMetadata(pxr::UsdGeomTokens->upAxis, &axis); + this->dataPtr->upAxis = axis.GetText(); + + if (this->dataPtr->upAxis != "Y" && this->dataPtr->upAxis != "Z") + { + errors.emplace_back( + UsdError( + sdf::usd::UsdErrorCode::INVALID_UP_AXIS, + "Up axis should be 'Y' or 'Z'")); + return errors; + } + } + + // Keep all the USD paths + auto range = pxr::UsdPrimRange::Stage(referencee); + for (auto const &_prim : range) + { + if (_prim.IsA()) + { + continue; + } + if (_prim.IsA()) + { + continue; + } + + this->dataPtr->paths.insert(_prim.GetPath().GetName()); + } + return errors; + } + + ///////////////////////////////////////////////// + const std::string &USDStage::UpAxis() const + { + return this->dataPtr->upAxis; + } + + ///////////////////////////////////////////////// + double USDStage::MetersPerUnit() const + { + return this->dataPtr->metersPerUnit; + } + + ///////////////////////////////////////////////// + const std::set &USDStage::USDPaths() const + { + return this->dataPtr->paths; + } +} // usd +} // sdf version namespace +} // sdf diff --git a/usd/src/usd_parser/USDStage_TEST.cc b/usd/src/usd_parser/USDStage_TEST.cc new file mode 100644 index 000000000..dcb4f814a --- /dev/null +++ b/usd/src/usd_parser/USDStage_TEST.cc @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * 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 + * + * http://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. + * +*/ + +#include + +#include + +#include + +#include +#include + +#include "test_config.h" +#include "test_utils.hh" + +///////////////////////////////////////////////// +TEST(USDStage, Constructor) +{ + // Check up Axis equal to Z and metersPerUnit + { + std::string filename = sdf::testing::TestFile("usd", "upAxisZ.usda"); + sdf::usd::USDStage stage(filename); + sdf::usd::UsdErrors errors = stage.Init(); + EXPECT_EQ(0u, errors.size()); + + EXPECT_EQ("Z", stage.UpAxis()); + EXPECT_DOUBLE_EQ(0.01, stage.MetersPerUnit()); + EXPECT_EQ(23u, stage.USDPaths().size()); + } + + // Check up Axis equal to Y and metersPerUnit + { + std::string filename = sdf::testing::TestFile("usd", "upAxisY.usda"); + sdf::usd::USDStage stage(filename); + sdf::usd::UsdErrors errors = stage.Init(); + EXPECT_EQ(0u, errors.size()); + + EXPECT_EQ("Y", stage.UpAxis()); + EXPECT_DOUBLE_EQ(1.0, stage.MetersPerUnit()); + EXPECT_EQ(10u, stage.USDPaths().size()); + } + + // Wrong upaxis + { + sdf::usd::USDStage stage( + sdf::testing::TestFile("usd", "/upAxis_wrong.usda")); + sdf::usd::UsdErrors errors = stage.Init(); + EXPECT_EQ(1u, errors.size()); + } + + // Invalid file + { + sdf::usd::USDStage stage(sdf::testing::TestFile("usd", "invalid_name")); + sdf::usd::UsdErrors errors = stage.Init(); + EXPECT_EQ(1u, errors.size()); + } +}