From e304686c9630206b0bf921fc3fd8223b459b3aef Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Thu, 13 Jan 2022 09:39:34 -0500 Subject: [PATCH 01/32] Added anisotropic parameters. --- fury/material.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/fury/material.py b/fury/material.py index ef42ae14a..fd0a9b804 100644 --- a/fury/material.py +++ b/fury/material.py @@ -5,13 +5,14 @@ from fury.lib import VTK_9_PLUS, VTK_OBJECT, calldata_type -def manifest_pbr(actor, metallicity=0, roughness=.5): +def manifest_pbr(actor, metallic=0, roughness=.5, anisotropy=0, + anisotropy_rotation=0, clearcoat=0): """Apply the Physically Based Rendering properties to the selected actor. Parameters ---------- actor : actor - metallicity : float, optional + metallic : float, optional Metallic or non-metallic (dielectric) shading computation value. Values must be between 0.0 and 1.0. roughness : float, optional @@ -28,8 +29,11 @@ def manifest_pbr(actor, metallicity=0, roughness=.5): prop = actor.GetProperty() try: prop.SetInterpolationToPBR() - prop.SetMetallic(metallicity) + prop.SetMetallic(metallic) prop.SetRoughness(roughness) + prop.SetAnisotropy(anisotropy) + prop.SetAnisotropyRotation(anisotropy_rotation) + #prop.SetClearcoat(clearcoat) except AttributeError: warnings.warn( 'PBR interpolation cannot be applied to this actor. The ' From 6515d763c8076aa1132091f127457a5709b05906 Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Fri, 14 Jan 2022 11:42:19 -0500 Subject: [PATCH 02/32] Added tangent utility functions. Added anisotropy to VTK's PBR demo. --- docs/tutorials/03_shaders/viz_pbr_spheres.py | 48 ++++++++++++++++++++ fury/utils.py | 42 ++++++++++++++++- 2 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 docs/tutorials/03_shaders/viz_pbr_spheres.py diff --git a/docs/tutorials/03_shaders/viz_pbr_spheres.py b/docs/tutorials/03_shaders/viz_pbr_spheres.py new file mode 100644 index 000000000..1a08182bb --- /dev/null +++ b/docs/tutorials/03_shaders/viz_pbr_spheres.py @@ -0,0 +1,48 @@ +from fury import actor, material, window +from fury.utils import (get_polydata_normals, normalize_v3, + set_polydata_tangents) +import numpy as np + +scene = window.Scene() +scene.background((.9, .9, .9)) + +material_params = [ + [[1, 1, 0], {'metallic': 0, 'roughness': 0}], + [(0, 0, 1), {'roughness': 0}], + [(1, 0, 1), {'anisotropy': 0, 'metallic': .25, 'roughness': .5}], + [(1, 0, 1), {'anisotropy_rotation': 0, 'anisotropy': 1, 'metallic': .25, + 'roughness': .5}] +] + +for i in range(4): + center = [[0, -5 * i, 0]] + for j in range(11): + center[0][0] = -25 + 5 * j + sphere = actor.sphere(center, material_params[i][0], radii=2, theta=32, + phi=32) + polydata = sphere.GetMapper().GetInput() + normals = get_polydata_normals(polydata) + tangents = np.cross(normals, np.array([0, 1, .5])) + binormals = normalize_v3(np.cross(normals, tangents)) + tangents = normalize_v3(np.cross(normals, binormals)) + set_polydata_tangents(polydata, tangents) + keys = list(material_params[i][1]) + material_params[i][1][keys[0]] = np.round(0.1 * j, decimals=1) + material.manifest_pbr(sphere, **material_params[i][1]) + scene.add(sphere) + +labels = ['Metallic', 'Roughness', 'Anisotropy', 'Anisotropy Rotation'] + +for i in range(4): + pos = [-40, -5 * i, 0] + label = actor.label(labels[i], pos=pos, scale=(.8, .8, .8), + color=(0, 0, 0)) + scene.add(label) + +for j in range(11): + pos = [-26 + 5 * j, 5, 0] + label = actor.label(str(np.round(j * 0.1, decimals=1)), pos=pos, + scale=(.8, .8, .8), color=(0, 0, 0)) + scene.add(label) + +window.show(scene) diff --git a/fury/utils.py b/fury/utils.py index a4c5bccd7..bd84a96c1 100644 --- a/fury/utils.py +++ b/fury/utils.py @@ -4,8 +4,9 @@ from fury.colormap import line_colors from fury.lib import (numpy_support, VTK_9_PLUS, PolyData, ImageData, Points, CellArray, PolyDataNormals, Actor, PolyDataMapper, - Matrix4x4, Matrix3x3, Glyph3D, VTK_DOUBLE, Transform, - AlgorithmOutput, VTK_UNSIGNED_CHAR, IdTypeArray) + Matrix4x4, Matrix3x3, Glyph3D, VTK_DOUBLE, VTK_FLOAT, + Transform, AlgorithmOutput, VTK_UNSIGNED_CHAR, + IdTypeArray) def remove_observer_from_actor(actor, id): @@ -379,6 +380,27 @@ def get_polydata_normals(polydata): return numpy_support.vtk_to_numpy(vtk_normals) +def get_polydata_tangents(polydata): + """Get vertices tangent (ndarrays Nx3 int) from a vtk polydata. + + Parameters + ---------- + polydata : vtkPolyData + + Returns + ------- + output : array (N, 3) + Tangents, represented as 2D ndarrays (Nx3). None if there are no + tangents in the vtk polydata. + + """ + vtk_tangents = polydata.GetPointData().GetTangents() + if vtk_tangents is None: + return None + + return numpy_support.vtk_to_numpy(vtk_tangents) + + def get_polydata_colors(polydata): """Get points color (ndarrays Nx3 int) from a vtk polydata. @@ -453,6 +475,22 @@ def set_polydata_normals(polydata, normals): return polydata +def set_polydata_tangents(polydata, tangents): + """Set polydata tangents with a numpy array (ndarrays Nx3 int). + + Parameters + ---------- + polydata : vtkPolyData + tangents : tangents, represented as 2D ndarrays (Nx3) (one per vertex) + + """ + vtk_tangents = numpy_support.numpy_to_vtk(tangents, deep=True, + array_type=VTK_FLOAT) + vtk_tangents.SetName('Tangents') + polydata.GetPointData().SetTangents(vtk_tangents) + return polydata + + def set_polydata_colors(polydata, colors, array_name="colors"): """Set polydata colors with a numpy array (ndarrays Nx3 int). From 832c29d5f2dfef664c97a886c2dfb7a9a002678c Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Fri, 14 Jan 2022 13:49:41 -0500 Subject: [PATCH 03/32] Added doscstrings for Anisotropic parameters. Added Clear coat strength and roughness parameters to both the material module and the tutorial. --- docs/tutorials/03_shaders/viz_pbr_spheres.py | 11 +++++++---- fury/material.py | 19 ++++++++++++++++--- fury/utils.py | 2 ++ 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/docs/tutorials/03_shaders/viz_pbr_spheres.py b/docs/tutorials/03_shaders/viz_pbr_spheres.py index 1a08182bb..a3f655720 100644 --- a/docs/tutorials/03_shaders/viz_pbr_spheres.py +++ b/docs/tutorials/03_shaders/viz_pbr_spheres.py @@ -11,10 +11,12 @@ [(0, 0, 1), {'roughness': 0}], [(1, 0, 1), {'anisotropy': 0, 'metallic': .25, 'roughness': .5}], [(1, 0, 1), {'anisotropy_rotation': 0, 'anisotropy': 1, 'metallic': .25, - 'roughness': .5}] + 'roughness': .5}], + [(0, 1, 1), {'coat_strength': 0, 'roughness': .5}], + [(0, 1, 1), {'coat_roughness': 0, 'coat_strength': 1, 'roughness': 0}] ] -for i in range(4): +for i in range(6): center = [[0, -5 * i, 0]] for j in range(11): center[0][0] = -25 + 5 * j @@ -31,9 +33,10 @@ material.manifest_pbr(sphere, **material_params[i][1]) scene.add(sphere) -labels = ['Metallic', 'Roughness', 'Anisotropy', 'Anisotropy Rotation'] +labels = ['Metallic', 'Roughness', 'Anisotropy', 'Anisotropy Rotation', + 'Coat Strength', 'Coat Roughness'] -for i in range(4): +for i in range(6): pos = [-40, -5 * i, 0] label = actor.label(labels[i], pos=pos, scale=(.8, .8, .8), color=(0, 0, 0)) diff --git a/fury/material.py b/fury/material.py index fd0a9b804..c0cc53762 100644 --- a/fury/material.py +++ b/fury/material.py @@ -6,8 +6,8 @@ def manifest_pbr(actor, metallic=0, roughness=.5, anisotropy=0, - anisotropy_rotation=0, clearcoat=0): - """Apply the Physically Based Rendering properties to the selected actor. + anisotropy_rotation=0, coat_strength=0, coat_roughness=0): + """Apply VTK's Physically Based Rendering properties to the selected actor. Parameters ---------- @@ -18,6 +18,18 @@ def manifest_pbr(actor, metallic=0, roughness=.5, anisotropy=0, roughness : float, optional Parameter used to specify how glossy the actor should be. Values must be between 0.0 and 1.0. + anisotropy : float, optional + Isotropic or anisotropic material parameter. Values must be between + 0.0 and 1.0. + anisotropy_rotation : float, optional + Rotation of the anisotropy around the normal in a counter-clockwise + fashion. Values must be between 0.0 and 1.0. A value of 1.0 means a + rotation of 2 * pi. + coat_strength : float, optional + Strength of the coat layer. Values must be between 0.0 and 1.0 (0.0 + means no clear coat will be modeled). + coat_roughness : float, optional + Roughness of the coat layer. Values must be between 0.0 and 1.0. """ if not VTK_9_PLUS: @@ -33,7 +45,8 @@ def manifest_pbr(actor, metallic=0, roughness=.5, anisotropy=0, prop.SetRoughness(roughness) prop.SetAnisotropy(anisotropy) prop.SetAnisotropyRotation(anisotropy_rotation) - #prop.SetClearcoat(clearcoat) + prop.SetCoatStrength(coat_strength) + prop.SetCoatRoughness(coat_roughness) except AttributeError: warnings.warn( 'PBR interpolation cannot be applied to this actor. The ' diff --git a/fury/utils.py b/fury/utils.py index bd84a96c1..e8d014d13 100644 --- a/fury/utils.py +++ b/fury/utils.py @@ -486,6 +486,8 @@ def set_polydata_tangents(polydata, tangents): """ vtk_tangents = numpy_support.numpy_to_vtk(tangents, deep=True, array_type=VTK_FLOAT) + # VTK does not require a specific name for the tangents array, however, for + # readability purposes, we set it to "Tangents" vtk_tangents.SetName('Tangents') polydata.GetPointData().SetTangents(vtk_tangents) return polydata From 504095b6185065c0cecabe8028f35e7707391c0d Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Fri, 14 Jan 2022 17:16:04 -0500 Subject: [PATCH 04/32] Added Index of Refraction parameters to material and tutorial. --- docs/tutorials/03_shaders/viz_pbr_spheres.py | 37 ++++++++++++++++++-- fury/material.py | 11 +++++- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/03_shaders/viz_pbr_spheres.py b/docs/tutorials/03_shaders/viz_pbr_spheres.py index a3f655720..3be915afb 100644 --- a/docs/tutorials/03_shaders/viz_pbr_spheres.py +++ b/docs/tutorials/03_shaders/viz_pbr_spheres.py @@ -12,7 +12,7 @@ [(1, 0, 1), {'anisotropy': 0, 'metallic': .25, 'roughness': .5}], [(1, 0, 1), {'anisotropy_rotation': 0, 'anisotropy': 1, 'metallic': .25, 'roughness': .5}], - [(0, 1, 1), {'coat_strength': 0, 'roughness': .5}], + [(0, 1, 1), {'coat_strength': 0, 'roughness': 0}], [(0, 1, 1), {'coat_roughness': 0, 'coat_strength': 1, 'roughness': 0}] ] @@ -43,9 +43,42 @@ scene.add(label) for j in range(11): - pos = [-26 + 5 * j, 5, 0] + pos = [-26 + 5 * j, 3, 0] label = actor.label(str(np.round(j * 0.1, decimals=1)), pos=pos, scale=(.8, .8, .8), color=(0, 0, 0)) scene.add(label) +iors = np.round(np.linspace(1, 2.3, num=11), decimals=2) + +ior_params = [ + [(0, 1, 1), {'base_ior': iors[0], 'roughness': 0}], + [(0, 1, 1), {'coat_ior': iors[0], 'coat_roughness': .1, 'coat_strength': 1, + 'roughness': 0}] +] + +for i in range(2): + center = [[0, -35 - (5 * i), 0]] + for j in range(11): + center[0][0] = -25 + 5 * j + sphere = actor.sphere(center, ior_params[i][0], radii=2, theta=32, + phi=32) + keys = list(ior_params[i][1]) + ior_params[i][1][keys[0]] = iors[j] + material.manifest_pbr(sphere, **ior_params[i][1]) + scene.add(sphere) + +labels = ['Base IoR', 'Coat IoR'] + +for i in range(2): + pos = [-40, -35 - (5 * i), 0] + label = actor.label(labels[i], pos=pos, scale=(.8, .8, .8), + color=(0, 0, 0)) + scene.add(label) + +for j in range(11): + pos = [-26 + 5 * j, -32, 0] + label = actor.label('{:.02f}'.format(iors[j]), pos=pos, scale=(.8, .8, .8), + color=(0, 0, 0)) + scene.add(label) + window.show(scene) diff --git a/fury/material.py b/fury/material.py index c0cc53762..b451bd0d6 100644 --- a/fury/material.py +++ b/fury/material.py @@ -6,7 +6,8 @@ def manifest_pbr(actor, metallic=0, roughness=.5, anisotropy=0, - anisotropy_rotation=0, coat_strength=0, coat_roughness=0): + anisotropy_rotation=0, coat_strength=0, coat_roughness=0, + base_ior=1.5, coat_ior=2): """Apply VTK's Physically Based Rendering properties to the selected actor. Parameters @@ -30,6 +31,12 @@ def manifest_pbr(actor, metallic=0, roughness=.5, anisotropy=0, means no clear coat will be modeled). coat_roughness : float, optional Roughness of the coat layer. Values must be between 0.0 and 1.0. + base_ior : float, optional + Index of refraction of the base material. Default is 1.5. Values must + be between 1.0 and 2.3. + coat_ior : float, optional + Index of refraction of the coat material. Default is 1.5. Values must + be between 1.0 and 2.3. """ if not VTK_9_PLUS: @@ -47,6 +54,8 @@ def manifest_pbr(actor, metallic=0, roughness=.5, anisotropy=0, prop.SetAnisotropyRotation(anisotropy_rotation) prop.SetCoatStrength(coat_strength) prop.SetCoatRoughness(coat_roughness) + prop.SetBaseIOR(base_ior) + prop.SetCoatIOR(coat_ior) except AttributeError: warnings.warn( 'PBR interpolation cannot be applied to this actor. The ' From a3996761e8ee6d3669e13d9f91e43189bd34f5e7 Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Wed, 19 Jan 2022 13:51:08 -0500 Subject: [PATCH 05/32] Added tests for tangent functions. --- fury/tests/test_utils.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/fury/tests/test_utils.py b/fury/tests/test_utils.py index e40ef2e36..56990c65f 100644 --- a/fury/tests/test_utils.py +++ b/fury/tests/test_utils.py @@ -2,8 +2,8 @@ import pytest import numpy as np import numpy.testing as npt -from fury.utils import (map_coordinates_3d_4d, - vtk_matrix_to_numpy, +from fury.utils import (get_polydata_tangents, map_coordinates_3d_4d, + set_polydata_tangents, vtk_matrix_to_numpy, numpy_to_vtk_matrix, get_grid_cells_position, rotate, vertices_from_actor, @@ -118,6 +118,26 @@ def test_polydata_polygon(interactive=False): npt.assert_equal(report.objects, 1) +def test_set_polydata_tangents(): + my_polydata = PolyData() + poly_point_data = my_polydata.GetPointData() + npt.assert_equal(poly_point_data.GetNumberOfArrays(), 0) + array = np.array([[0, 0, 0], [1, 1, 1]]) + set_polydata_tangents(my_polydata, array) + npt.assert_equal(poly_point_data.GetNumberOfArrays(), 1) + npt.assert_equal(poly_point_data.HasArray('Tangents'), True) + + +def test_get_polydata_tangents(): + my_polydata = PolyData() + tangents = get_polydata_tangents(my_polydata) + npt.assert_equal(tangents, None) + array = np.array([[0, 0, 0], [1, 1, 1]]) + set_polydata_tangents(my_polydata, array) + tangents = get_polydata_tangents(my_polydata) + npt.assert_array_equal(tangents, array) + + def test_asbytes(): text = [b'test', 'test'] From efc41d61597ba16358d1e991b1c0c04afadbcba8 Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Wed, 19 Jan 2022 16:47:01 -0500 Subject: [PATCH 06/32] Added tutorial descriptions. Replaced label actors by vector_text actors. --- docs/tutorials/03_shaders/viz_pbr_spheres.py | 73 +++++++++++++++++--- 1 file changed, 64 insertions(+), 9 deletions(-) diff --git a/docs/tutorials/03_shaders/viz_pbr_spheres.py b/docs/tutorials/03_shaders/viz_pbr_spheres.py index 3be915afb..90b6a9db8 100644 --- a/docs/tutorials/03_shaders/viz_pbr_spheres.py +++ b/docs/tutorials/03_shaders/viz_pbr_spheres.py @@ -1,11 +1,37 @@ +""" +=============================================================================== +Physically-Based Rendering (PBR) on spheres +=============================================================================== + +PBR engines aim to simulate properties of light when it interacts with objects +in the scene in a physically plausible way. The interaction of light with an +object depends on the material the object is made of. In computer graphics, +materials are usually divided in 2 main categories based on their conductive +properties: dielectrics and metals. + +This tutorial, illustrates how to model some material properties in FURY by +using the PBR material. + +Let's start by importing the necessary modules: +""" + from fury import actor, material, window from fury.utils import (get_polydata_normals, normalize_v3, set_polydata_tangents) import numpy as np +""" +Now set up a new scene. +""" + scene = window.Scene() scene.background((.9, .9, .9)) +""" +Let's define the parameters we are going to showcase in this tutorial. +These subset of parameters have their values constrained in the 0 to 1 range. +""" + material_params = [ [[1, 1, 0], {'metallic': 0, 'roughness': 0}], [(0, 0, 1), {'roughness': 0}], @@ -16,6 +42,11 @@ [(0, 1, 1), {'coat_roughness': 0, 'coat_strength': 1, 'roughness': 0}] ] +""" +Now we can start to add our actors to the scene and see how different values of +the parameters produce interesting effects. +""" + for i in range(6): center = [[0, -5 * i, 0]] for j in range(11): @@ -33,21 +64,33 @@ material.manifest_pbr(sphere, **material_params[i][1]) scene.add(sphere) +""" +For interpretability purposes we will add some labels to guide us through our +visualization. +""" + labels = ['Metallic', 'Roughness', 'Anisotropy', 'Anisotropy Rotation', 'Coat Strength', 'Coat Roughness'] for i in range(6): pos = [-40, -5 * i, 0] - label = actor.label(labels[i], pos=pos, scale=(.8, .8, .8), - color=(0, 0, 0)) + label = actor.vector_text(labels[i], pos=pos, scale=(.8, .8, .8), + color=(0, 0, 0)) scene.add(label) for j in range(11): pos = [-26 + 5 * j, 3, 0] - label = actor.label(str(np.round(j * 0.1, decimals=1)), pos=pos, - scale=(.8, .8, .8), color=(0, 0, 0)) + label = actor.vector_text(str(np.round(j * 0.1, decimals=1)), pos=pos, + scale=(.8, .8, .8), color=(0, 0, 0)) scene.add(label) +""" +Some parameters of this material have their values constrained to be between 1 +and 2.3. These parameters are the Base Index of Refraction (IOR) and the Clear +coat Index of Refraction (IOR). Therefore, we will interpolate some values +within this range and see how they affect the rendering. +""" + iors = np.round(np.linspace(1, 2.3, num=11), decimals=2) ior_params = [ @@ -67,18 +110,30 @@ material.manifest_pbr(sphere, **ior_params[i][1]) scene.add(sphere) +""" +Let's add the respective labels to the scene. +""" + labels = ['Base IoR', 'Coat IoR'] for i in range(2): pos = [-40, -35 - (5 * i), 0] - label = actor.label(labels[i], pos=pos, scale=(.8, .8, .8), - color=(0, 0, 0)) + label = actor.vector_text(labels[i], pos=pos, scale=(.8, .8, .8), + color=(0, 0, 0)) scene.add(label) for j in range(11): pos = [-26 + 5 * j, -32, 0] - label = actor.label('{:.02f}'.format(iors[j]), pos=pos, scale=(.8, .8, .8), - color=(0, 0, 0)) + label = actor.vector_text('{:.02f}'.format(iors[j]), pos=pos, + scale=(.8, .8, .8), color=(0, 0, 0)) scene.add(label) -window.show(scene) +""" +Finally, let's visualize our demo. +""" + +interactive = False +if interactive: + window.show(scene) + +window.record(scene, size=(600, 600), out_path="viz_pbr_spheres.png") From 45df6ebccb3b19e58abe78688da0ba64ab0a967d Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Wed, 19 Jan 2022 19:51:09 -0500 Subject: [PATCH 07/32] Added actors and skybox for interactive demo. --- docs/examples/viz_pbr_interactive.py | 88 ++++++++++++++++++++++++++++ fury/lib.py | 3 + 2 files changed, 91 insertions(+) create mode 100644 docs/examples/viz_pbr_interactive.py diff --git a/docs/examples/viz_pbr_interactive.py b/docs/examples/viz_pbr_interactive.py new file mode 100644 index 000000000..cea1acb88 --- /dev/null +++ b/docs/examples/viz_pbr_interactive.py @@ -0,0 +1,88 @@ +from fury import actor, material, window +from fury.data import fetch_viz_textures, read_viz_textures +from fury.lib import (VTK_9_PLUS, VTK_UNSIGNED_INT, ImageFlip, + ImageReader2Factory, Skybox, Texture, numpy_support) +import numpy as np +import os + + +def get_cubemap_texture(file_names, interpolate_on=True, mipmap_on=True): + texture = Texture() + texture.CubeMapOn() + for idx, fn in enumerate(file_names): + if not os.path.isfile(fn): + raise FileNotFoundError(fn) + else: + # Read the images + reader_factory = ImageReader2Factory() + img_reader = reader_factory.CreateImageReader2(fn) + img_reader.SetFileName(fn) + + flip = ImageFlip() + flip.SetInputConnection(img_reader.GetOutputPort()) + flip.SetFilteredAxis(1) # flip y axis + texture.SetInputConnection(idx, flip.GetOutputPort(0)) + if interpolate_on: + texture.InterpolateOn() + if mipmap_on: + texture.MipmapOn() + return texture + + +# TODO: Fetch skybox +# TODO: Create wrapper function with name order +texture_name = 'skybox' +cubemap_fns = [read_viz_textures(texture_name + '-px.jpg'), + read_viz_textures(texture_name + '-nx.jpg'), + read_viz_textures(texture_name + '-py.jpg'), + read_viz_textures(texture_name + '-ny.jpg'), + read_viz_textures(texture_name + '-pz.jpg'), + read_viz_textures(texture_name + '-nz.jpg')] + +cubemap = get_cubemap_texture(cubemap_fns) + +scene = window.Scene() + +print(scene.GetUseImageBasedLighting()) + +# TODO: Add to Scene constructor +scene.UseImageBasedLightingOn() +if VTK_9_PLUS: + scene.SetEnvironmentTexture(cubemap) +else: + scene.SetEnvironmentCubeMap(cubemap) + +print(scene.GetUseImageBasedLighting()) + +#skybox.RepeatOff() +#skybox.EdgeClampOn() + +skybox = Skybox() +skybox.SetTexture(cubemap) + +scene.add(skybox) + +sphere = actor.sphere([[0, 0, 0]], (.7, .7, .7), radii=2, theta=64, phi=64) +polydata = sphere.GetMapper().GetInput() + +print(polydata.GetFieldData()) + +# TODO: get_polydata_field/field_from_actor +print(polydata.GetFieldData().GetArray('Uses IBL')) + +# TODO: add_polydata_field/field_to_actor +field = np.array([[True]]) +field_name = 'Uses IBL' +array_type = VTK_UNSIGNED_INT +vtk_field = numpy_support.numpy_to_vtk(field, deep=True, array_type=array_type) +vtk_field.SetName(field_name) +polydata.GetFieldData().AddArray(vtk_field) + +print(polydata.GetFieldData()) + +# TODO: get_polydata_field/field_from_actor +print(numpy_support.vtk_to_numpy(polydata.GetFieldData().GetArray('Uses IBL'))) + +scene.add(sphere) + +window.show(scene) diff --git a/fury/lib.py b/fury/lib.py index 46f8d2664..a72001b1a 100644 --- a/fury/lib.py +++ b/fury/lib.py @@ -63,6 +63,7 @@ ############################################################## # vtkRenderingCore Module Renderer = rcvtk.vtkRenderer +Skybox = rcvtk.vtkSkybox Volume = rcvtk.vtkVolume Actor2D = rcvtk.vtkActor2D Actor = rcvtk.vtkActor @@ -171,12 +172,14 @@ ############################################################## # vtkImagingCore Module +ImageFlip = icvtk.vtkImageFlip ImageReslice = icvtk.vtkImageReslice ImageMapToColors = icvtk.vtkImageMapToColors ############################################################## # vtkIOImage vtkIOLegacy, vtkIOPLY, vtkIOGeometry, # vtkIOMINC Modules +ImageReader2Factory = ioivtk.vtkImageReader2Factory PNGReader = ioivtk.vtkPNGReader BMPReader = ioivtk.vtkBMPReader JPEGReader = ioivtk.vtkJPEGReader From ccf059830661e9232470e8e40164c5a4d0e2d7bc Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Thu, 20 Jan 2022 09:49:24 -0500 Subject: [PATCH 08/32] Added add_polydata_numeric_field and get_polydata_field functions. --- docs/examples/viz_pbr_interactive.py | 18 +++--- fury/lib.py | 3 +- fury/tests/test_utils.py | 86 ++++++++++++++++++++++++---- fury/utils.py | 46 ++++++++++++++- 4 files changed, 131 insertions(+), 22 deletions(-) diff --git a/docs/examples/viz_pbr_interactive.py b/docs/examples/viz_pbr_interactive.py index cea1acb88..95ba9f33d 100644 --- a/docs/examples/viz_pbr_interactive.py +++ b/docs/examples/viz_pbr_interactive.py @@ -2,6 +2,7 @@ from fury.data import fetch_viz_textures, read_viz_textures from fury.lib import (VTK_9_PLUS, VTK_UNSIGNED_INT, ImageFlip, ImageReader2Factory, Skybox, Texture, numpy_support) +from fury.utils import add_polydata_numeric_field, get_polydata_field import numpy as np import os @@ -67,21 +68,18 @@ def get_cubemap_texture(file_names, interpolate_on=True, mipmap_on=True): print(polydata.GetFieldData()) -# TODO: get_polydata_field/field_from_actor -print(polydata.GetFieldData().GetArray('Uses IBL')) +# TODO: field_from_actor +print(get_polydata_field(polydata, 'Uses IBL')) -# TODO: add_polydata_field/field_to_actor -field = np.array([[True]]) +# TODO: field_to_actor +field = True field_name = 'Uses IBL' -array_type = VTK_UNSIGNED_INT -vtk_field = numpy_support.numpy_to_vtk(field, deep=True, array_type=array_type) -vtk_field.SetName(field_name) -polydata.GetFieldData().AddArray(vtk_field) +add_polydata_numeric_field(polydata, field_name, field) print(polydata.GetFieldData()) -# TODO: get_polydata_field/field_from_actor -print(numpy_support.vtk_to_numpy(polydata.GetFieldData().GetArray('Uses IBL'))) +# TODO: field_from_actor +print(get_polydata_field(polydata, 'Uses IBL')) scene.add(sphere) diff --git a/fury/lib.py b/fury/lib.py index a72001b1a..74c4c0bdd 100644 --- a/fury/lib.py +++ b/fury/lib.py @@ -45,6 +45,7 @@ UnsignedCharArray = ccvtk.vtkUnsignedCharArray VTK_OBJECT = ccvtk.VTK_OBJECT VTK_ID_TYPE = ccvtk.VTK_ID_TYPE +VTK_INT = ccvtk.VTK_INT VTK_DOUBLE = ccvtk.VTK_DOUBLE VTK_FLOAT = ccvtk.VTK_FLOAT VTK_TEXT_LEFT = ccvtk.VTK_TEXT_LEFT @@ -53,8 +54,8 @@ VTK_TEXT_TOP = ccvtk.VTK_TEXT_TOP VTK_TEXT_CENTERED = ccvtk.VTK_TEXT_CENTERED VTK_UNSIGNED_CHAR = ccvtk.VTK_UNSIGNED_CHAR -VTK_UNSIGNED_SHORT = ccvtk.VTK_UNSIGNED_SHORT VTK_UNSIGNED_INT = ccvtk.VTK_UNSIGNED_INT +VTK_UNSIGNED_SHORT = ccvtk.VTK_UNSIGNED_SHORT ############################################################## # vtkCommonExecutionModel Module diff --git a/fury/tests/test_utils.py b/fury/tests/test_utils.py index 56990c65f..28099cca1 100644 --- a/fury/tests/test_utils.py +++ b/fury/tests/test_utils.py @@ -2,7 +2,8 @@ import pytest import numpy as np import numpy.testing as npt -from fury.utils import (get_polydata_tangents, map_coordinates_3d_4d, +from fury.utils import (add_polydata_numeric_field, get_polydata_field, + get_polydata_tangents, map_coordinates_3d_4d, set_polydata_tangents, vtk_matrix_to_numpy, numpy_to_vtk_matrix, get_grid_cells_position, @@ -13,7 +14,7 @@ from fury import actor, window, utils from fury.lib import (numpy_support, PolyData, PolyDataMapper2D, Points, CellArray, Polygon, Actor2D, DoubleArray, - UnsignedCharArray) + UnsignedCharArray, VTK_DOUBLE, VTK_INT, VTK_FLOAT) import fury.primitive as fp @@ -118,14 +119,68 @@ def test_polydata_polygon(interactive=False): npt.assert_equal(report.objects, 1) -def test_set_polydata_tangents(): +def test_add_polydata_numeric_field(): my_polydata = PolyData() - poly_point_data = my_polydata.GetPointData() - npt.assert_equal(poly_point_data.GetNumberOfArrays(), 0) - array = np.array([[0, 0, 0], [1, 1, 1]]) - set_polydata_tangents(my_polydata, array) - npt.assert_equal(poly_point_data.GetNumberOfArrays(), 1) - npt.assert_equal(poly_point_data.HasArray('Tangents'), True) + poly_field_data = my_polydata.GetFieldData() + npt.assert_equal(poly_field_data.GetNumberOfArrays(), 0) + bool_data = True + add_polydata_numeric_field(my_polydata, 'Test Bool', bool_data) + npt.assert_equal(poly_field_data.GetNumberOfArrays(), 1) + npt.assert_equal(poly_field_data.GetArray('Test Bool').GetValue(0), + bool_data) + poly_field_data.RemoveArray('Test Bool') + npt.assert_equal(poly_field_data.GetNumberOfArrays(), 0) + int_data = 1 + add_polydata_numeric_field(my_polydata, 'Test Int', int_data) + npt.assert_equal(poly_field_data.GetNumberOfArrays(), 1) + npt.assert_equal(poly_field_data.GetArray('Test Int').GetValue(0), + int_data) + poly_field_data.RemoveArray('Test Int') + npt.assert_equal(poly_field_data.GetNumberOfArrays(), 0) + float_data = .1 + add_polydata_numeric_field(my_polydata, 'Test Float', float_data, + array_type=VTK_FLOAT) + npt.assert_equal(poly_field_data.GetNumberOfArrays(), 1) + npt.assert_almost_equal(poly_field_data.GetArray('Test Float').GetValue(0), + float_data) + poly_field_data.RemoveArray('Test Float') + npt.assert_equal(poly_field_data.GetNumberOfArrays(), 0) + double_data = .1 + add_polydata_numeric_field(my_polydata, 'Test Double', double_data, + array_type=VTK_DOUBLE) + npt.assert_equal(poly_field_data.GetNumberOfArrays(), 1) + npt.assert_equal(poly_field_data.GetArray('Test Double').GetValue(0), + double_data) + poly_field_data.RemoveArray('Test Double') + npt.assert_equal(poly_field_data.GetNumberOfArrays(), 0) + array_data = [-1, 0, 1] + add_polydata_numeric_field(my_polydata, 'Test Array', array_data) + npt.assert_equal(poly_field_data.GetNumberOfArrays(), 1) + npt.assert_equal( + numpy_support.vtk_to_numpy(poly_field_data.GetArray('Test Array')), + array_data) + poly_field_data.RemoveArray('Test Array') + npt.assert_equal(poly_field_data.GetNumberOfArrays(), 0) + ndarray_data = np.array([[-.1, -.1], [0, 0], [.1, .1]]) + add_polydata_numeric_field(my_polydata, 'Test NDArray', ndarray_data, + array_type=VTK_FLOAT) + npt.assert_equal(poly_field_data.GetNumberOfArrays(), 1) + npt.assert_almost_equal( + numpy_support.vtk_to_numpy(poly_field_data.GetArray('Test NDArray')), + ndarray_data) + + +def test_get_polydata_field(): + my_polydata = PolyData() + field_data = get_polydata_field(my_polydata, 'Test') + npt.assert_equal(field_data, None) + data = 1 + field_name = 'Test' + vtk_data = numpy_support.numpy_to_vtk(data) + vtk_data.SetName(field_name) + my_polydata.GetFieldData().AddArray(vtk_data) + field_data = get_polydata_field(my_polydata, field_name) + npt.assert_equal(field_data, data) def test_get_polydata_tangents(): @@ -133,11 +188,22 @@ def test_get_polydata_tangents(): tangents = get_polydata_tangents(my_polydata) npt.assert_equal(tangents, None) array = np.array([[0, 0, 0], [1, 1, 1]]) - set_polydata_tangents(my_polydata, array) + my_polydata.GetPointData().SetTangents( + numpy_support.numpy_to_vtk(array, deep=True, array_type=VTK_FLOAT)) tangents = get_polydata_tangents(my_polydata) npt.assert_array_equal(tangents, array) +def test_set_polydata_tangents(): + my_polydata = PolyData() + poly_point_data = my_polydata.GetPointData() + npt.assert_equal(poly_point_data.GetNumberOfArrays(), 0) + array = np.array([[0, 0, 0], [1, 1, 1]]) + set_polydata_tangents(my_polydata, array) + npt.assert_equal(poly_point_data.GetNumberOfArrays(), 1) + npt.assert_equal(poly_point_data.HasArray('Tangents'), True) + + def test_asbytes(): text = [b'test', 'test'] diff --git a/fury/utils.py b/fury/utils.py index e8d014d13..c2c1e2d0a 100644 --- a/fury/utils.py +++ b/fury/utils.py @@ -5,7 +5,7 @@ from fury.lib import (numpy_support, VTK_9_PLUS, PolyData, ImageData, Points, CellArray, PolyDataNormals, Actor, PolyDataMapper, Matrix4x4, Matrix3x3, Glyph3D, VTK_DOUBLE, VTK_FLOAT, - Transform, AlgorithmOutput, VTK_UNSIGNED_CHAR, + Transform, AlgorithmOutput, VTK_INT, VTK_UNSIGNED_CHAR, IdTypeArray) @@ -421,6 +421,50 @@ def get_polydata_colors(polydata): return numpy_support.vtk_to_numpy(vtk_colors) +def get_polydata_field(polydata, field_name, as_vtk=False): + """Get a field from a vtk polydata. + + Parameters + ---------- + polydata : vtkPolyData + field_name : str + as_vtk : optional + By default, ndarray is returned. + + Returns + ------- + output : ndarray or vtkDataArray + Field data. The return type depends on the value of the as_vtk + parameter. None if the field is not found. + + """ + vtk_field_data = polydata.GetFieldData().GetArray(field_name) + if vtk_field_data is None: + return None + if as_vtk: + return vtk_field_data + return numpy_support.vtk_to_numpy(vtk_field_data) + + +def add_polydata_numeric_field(polydata, field_name, field_data, + array_type=VTK_INT): + """Add a field to a vtk polydata. + + Parameters + ---------- + polydata : vtkPolyData + field_name : str + field_data : bool, int, float, double, numeric array or ndarray + array_type : vtkArrayType + + """ + vtk_field_data = numpy_support.numpy_to_vtk(field_data, deep=True, + array_type=array_type) + vtk_field_data.SetName(field_name) + polydata.GetFieldData().AddArray(vtk_field_data) + return polydata + + def set_polydata_triangles(polydata, triangles): """Set polydata triangles with a numpy array (ndarrays Nx3 int). From c8485b875725274e4ef2789734de98fc46288893 Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Thu, 20 Jan 2022 09:54:16 -0500 Subject: [PATCH 09/32] Added missing skybox-xy.jpg file to fetch_viz_textures. --- fury/data/fetcher.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/fury/data/fetcher.py b/fury/data/fetcher.py index 87e846380..07ac7fc2b 100644 --- a/fury/data/fetcher.py +++ b/fury/data/fetcher.py @@ -323,9 +323,9 @@ def fetcher(): ['1_earth_8k.jpg', '2_no_clouds_8k.jpg', '5_night_8k.jpg', 'earth.ppm', 'jupiter.jpg', 'masonry.bmp', - 'skybox-nx.jpg', 'skybox-ny.jpg', - 'skybox-px.jpg', 'skybox-py.jpg', - 'skybox-pz.jpg', 'moon_8k.jpg', + 'skybox-nx.jpg', 'skybox-ny.jpg', 'skybox-nz.jpg', + 'skybox-px.jpg', 'skybox-py.jpg', 'skybox-pz.jpg', + 'moon_8k.jpg', '8k_mercury.jpg', '8k_venus_surface.jpg', '8k_mars.jpg', '8k_saturn.jpg', '8k_saturn_ring_alpha.png', @@ -335,9 +335,9 @@ def fetcher(): ['1_earth_8k.jpg', '2_no_clouds_8k.jpg', '5_night_8k.jpg', 'earth.ppm', 'jupiter.jpg', 'masonry.bmp', - 'skybox-nx.jpg', 'skybox-ny.jpg', - 'skybox-px.jpg', 'skybox-py.jpg', - 'skybox-pz.jpg', 'moon-8k.jpg', + 'skybox-nx.jpg', 'skybox-ny.jpg', 'skybox-nz.jpg', + 'skybox-px.jpg', 'skybox-py.jpg', 'skybox-pz.jpg', + 'moon-8k.jpg', '8k_mercury.jpg', '8k_venus_surface.jpg', '8k_mars.jpg', '8k_saturn.jpg', '8k_saturn_ring_alpha.png', From 5d577fdb6a8d7dee22f064ca4ec24f85d8238416 Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Thu, 20 Jan 2022 18:01:46 -0500 Subject: [PATCH 10/32] Added *_from_actor and *_to_actor functions. Reduced complexity of viz_pbr_spheres tutorial. --- docs/tutorials/03_shaders/viz_pbr_spheres.py | 13 ++- fury/tests/test_utils.py | 57 ++++++++++++- fury/utils.py | 89 ++++++++++++++++++++ 3 files changed, 150 insertions(+), 9 deletions(-) diff --git a/docs/tutorials/03_shaders/viz_pbr_spheres.py b/docs/tutorials/03_shaders/viz_pbr_spheres.py index 90b6a9db8..6b3ea24fa 100644 --- a/docs/tutorials/03_shaders/viz_pbr_spheres.py +++ b/docs/tutorials/03_shaders/viz_pbr_spheres.py @@ -16,8 +16,8 @@ """ from fury import actor, material, window -from fury.utils import (get_polydata_normals, normalize_v3, - set_polydata_tangents) +from fury.utils import (normals_from_actor, tangents_to_actor, + tangents_from_direction_of_anisotropy) import numpy as np """ @@ -53,12 +53,9 @@ center[0][0] = -25 + 5 * j sphere = actor.sphere(center, material_params[i][0], radii=2, theta=32, phi=32) - polydata = sphere.GetMapper().GetInput() - normals = get_polydata_normals(polydata) - tangents = np.cross(normals, np.array([0, 1, .5])) - binormals = normalize_v3(np.cross(normals, tangents)) - tangents = normalize_v3(np.cross(normals, binormals)) - set_polydata_tangents(polydata, tangents) + normals = normals_from_actor(sphere) + tangents = tangents_from_direction_of_anisotropy(normals, (0, 1, .5)) + tangents_to_actor(sphere, tangents) keys = list(material_params[i][1]) material_params[i][1][keys[0]] = np.round(0.1 * j, decimals=1) material.manifest_pbr(sphere, **material_params[i][1]) diff --git a/fury/tests/test_utils.py b/fury/tests/test_utils.py index 28099cca1..64a6a7607 100644 --- a/fury/tests/test_utils.py +++ b/fury/tests/test_utils.py @@ -4,7 +4,10 @@ import numpy.testing as npt from fury.utils import (add_polydata_numeric_field, get_polydata_field, get_polydata_tangents, map_coordinates_3d_4d, - set_polydata_tangents, vtk_matrix_to_numpy, + normals_from_actor, normals_to_actor, + set_polydata_tangents, tangents_from_actor, + tangents_from_direction_of_anisotropy, + tangents_to_actor, vtk_matrix_to_numpy, numpy_to_vtk_matrix, get_grid_cells_position, rotate, vertices_from_actor, @@ -448,6 +451,58 @@ def test_vertices_from_actor(interactive=False): npt.assert_equal(isinstance(l_array_vtk, UnsignedCharArray), True) +def test_normals_from_actor(): + my_actor = actor.square(np.array([[0, 0, 0]])) + normals = normals_from_actor(my_actor) + npt.assert_equal(normals, None) + array = np.array([[0, 0, 0], [1, 1, 1], [2, 2, 2], [3, 3, 3]]) + my_actor.GetMapper().GetInput().GetPointData().SetNormals( + numpy_support.numpy_to_vtk(array, deep=True)) + normals = normals_from_actor(my_actor) + npt.assert_array_equal(normals, array) + + +def test_normals_to_actor(): + my_actor = actor.square(np.array([[0, 0, 0]])) + poly_point_data = my_actor.GetMapper().GetInput().GetPointData() + npt.assert_equal(poly_point_data.HasArray('Normals'), False) + array = np.array([[0, 0, 0], [1, 1, 1], [2, 2, 2], [3, 3, 3]]) + normals_to_actor(my_actor, array) + npt.assert_equal(poly_point_data.HasArray('Normals'), True) + normals = numpy_support.vtk_to_numpy(poly_point_data.GetArray('Normals')) + npt.assert_array_equal(normals, array) + + +def test_tangents_from_actor(): + my_actor = actor.square(np.array([[0, 0, 0]])) + tangents = tangents_from_actor(my_actor) + npt.assert_equal(tangents, None) + array = np.array([[0, 0, 0], [1, 1, 1], [2, 2, 2], [3, 3, 3]]) + my_actor.GetMapper().GetInput().GetPointData().SetTangents( + numpy_support.numpy_to_vtk(array, deep=True, array_type=VTK_FLOAT)) + tangents = tangents_from_actor(my_actor) + npt.assert_array_equal(tangents, array) + + +def test_tangents_from_direction_of_anisotropy(): + normals = np.array([[-1., 0., 0.], [0., 0., 1.]]) + doa = (0., 1., 0.) + expected = np.array([[0., 0., 1.], [1., 0., 0.]]) + actual = tangents_from_direction_of_anisotropy(normals, doa) + npt.assert_array_equal(actual, expected) + + +def test_tangents_to_actor(): + my_actor = actor.square(np.array([[0, 0, 0]])) + poly_point_data = my_actor.GetMapper().GetInput().GetPointData() + npt.assert_equal(poly_point_data.HasArray('Tangents'), False) + array = np.array([[0, 0, 0], [1, 1, 1], [2, 2, 2], [3, 3, 3]]) + tangents_to_actor(my_actor, array) + npt.assert_equal(poly_point_data.HasArray('Tangents'), True) + tangents = numpy_support.vtk_to_numpy(poly_point_data.GetArray('Tangents')) + npt.assert_array_equal(tangents, array) + + def test_get_actor_from_primitive(): vertices, triangles = fp.prim_frustum() colors = np.array([1, 0, 0]) diff --git a/fury/utils.py b/fury/utils.py index c2c1e2d0a..57e28de5e 100644 --- a/fury/utils.py +++ b/fury/utils.py @@ -515,6 +515,9 @@ def set_polydata_normals(polydata, normals): """ vtk_normals = numpy_support.numpy_to_vtk(normals, deep=True) + # VTK does not require a specific name for the normals array, however, for + # readability purposes, we set it to "Normals" + vtk_normals.SetName('Normals') polydata.GetPointData().SetNormals(vtk_normals) return polydata @@ -1028,6 +1031,26 @@ def normals_from_v_f(vertices, faces): return norm +def tangents_from_direction_of_anisotropy(normals, direction): + """Calculate tangents from normals and a 3D vector representing the + direction of anisotropy. + + Parameters + ---------- + normals : normals, represented as 2D ndarrays (Nx3) (one per vertex) + direction : tuple (3,) or array (3,) + + Returns + ------- + output : array (N, 3) + Tangents, represented as 2D ndarrays (Nx3). + + """ + tangents = np.cross(normals, direction) + binormals = normalize_v3(np.cross(normals, tangents)) + return normalize_v3(np.cross(normals, binormals)) + + def triangle_order(vertices, faces): """Determine the winding order of a given set of vertices and a triangle. @@ -1152,6 +1175,40 @@ def colors_from_actor(actor, array_name='colors', as_vtk=False): as_vtk=as_vtk) +def normals_from_actor(actor): + """Access normals from actor which uses polydata. + + Parameters + ---------- + actor : actor + + Returns + ------- + output : array (N, 3) + Normals + + """ + polydata = actor.GetMapper().GetInput() + return get_polydata_normals(polydata) + + +def tangents_from_actor(actor): + """Access tangents from actor which uses polydata. + + Parameters + ---------- + actor : actor + + Returns + ------- + output : array (N, 3) + Tangents + + """ + polydata = actor.GetMapper().GetInput() + return get_polydata_tangents(polydata) + + def array_from_actor(actor, array_name, as_vtk=False): """Access array from actor which uses polydata. @@ -1177,6 +1234,38 @@ def array_from_actor(actor, array_name, as_vtk=False): return numpy_support.vtk_to_numpy(vtk_array) +def normals_to_actor(actor, normals): + """Set normals to actor which uses polydata. + + Parameters + ---------- + actor : actor + normals : normals, represented as 2D ndarrays (Nx3) (one per vertex) + + Returns + ------- + actor + + """ + polydata = actor.GetMapper().GetInput() + set_polydata_normals(polydata, normals) + return actor + + +def tangents_to_actor(actor, tangents): + """Set tangents to actor which uses polydata. + + Parameters + ---------- + actor : actor + tangents : tangents, represented as 2D ndarrays (Nx3) (one per vertex) + + """ + polydata = actor.GetMapper().GetInput() + set_polydata_tangents(polydata, tangents) + return actor + + def compute_bounds(actor): """Compute Bounds of actor. From 66a65501fdfb8dfd1ccba27706da510f45628370 Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Tue, 25 Jan 2022 18:50:43 -0500 Subject: [PATCH 11/32] Added skybox_tex and render_skybox parameters to Scene. --- docs/examples/viz_pbr_interactive.py | 27 +-------- fury/lib.py | 1 + fury/tests/test_window.py | 84 +++++++++++++++++++--------- fury/window.py | 27 +++++++-- 4 files changed, 86 insertions(+), 53 deletions(-) diff --git a/docs/examples/viz_pbr_interactive.py b/docs/examples/viz_pbr_interactive.py index 95ba9f33d..1ce50e3e2 100644 --- a/docs/examples/viz_pbr_interactive.py +++ b/docs/examples/viz_pbr_interactive.py @@ -1,7 +1,6 @@ from fury import actor, material, window from fury.data import fetch_viz_textures, read_viz_textures -from fury.lib import (VTK_9_PLUS, VTK_UNSIGNED_INT, ImageFlip, - ImageReader2Factory, Skybox, Texture, numpy_support) +from fury.lib import ImageFlip, ImageReader2Factory, Texture from fury.utils import add_polydata_numeric_field, get_polydata_field import numpy as np import os @@ -42,42 +41,22 @@ def get_cubemap_texture(file_names, interpolate_on=True, mipmap_on=True): cubemap = get_cubemap_texture(cubemap_fns) -scene = window.Scene() - -print(scene.GetUseImageBasedLighting()) - -# TODO: Add to Scene constructor -scene.UseImageBasedLightingOn() -if VTK_9_PLUS: - scene.SetEnvironmentTexture(cubemap) -else: - scene.SetEnvironmentCubeMap(cubemap) - -print(scene.GetUseImageBasedLighting()) +scene = window.Scene(skybox_tex=cubemap, render_skybox=True) #skybox.RepeatOff() #skybox.EdgeClampOn() -skybox = Skybox() -skybox.SetTexture(cubemap) - -scene.add(skybox) - sphere = actor.sphere([[0, 0, 0]], (.7, .7, .7), radii=2, theta=64, phi=64) polydata = sphere.GetMapper().GetInput() -print(polydata.GetFieldData()) - # TODO: field_from_actor print(get_polydata_field(polydata, 'Uses IBL')) # TODO: field_to_actor -field = True +field = scene.GetUseImageBasedLighting() field_name = 'Uses IBL' add_polydata_numeric_field(polydata, field_name, field) -print(polydata.GetFieldData()) - # TODO: field_from_actor print(get_polydata_field(polydata, 'Uses IBL')) diff --git a/fury/lib.py b/fury/lib.py index 74c4c0bdd..3b223454c 100644 --- a/fury/lib.py +++ b/fury/lib.py @@ -104,6 +104,7 @@ ############################################################## # vtkRenderingOpenGL2 Module +OpenGLRenderer = roglvtk.vtkOpenGLRenderer Shader = roglvtk.vtkShader ############################################################## diff --git a/fury/tests/test_window.py b/fury/tests/test_window.py index d31cde5c3..c1ecb8d8e 100644 --- a/fury/tests/test_window.py +++ b/fury/tests/test_window.py @@ -6,73 +6,67 @@ import numpy.testing as npt import pytest from fury import actor, window, io +from fury.lib import ImageData, Texture from fury.testing import captured_output, assert_less_equal, assert_greater from fury.decorators import skip_osx, skip_win from fury import shaders +from vtk.util import numpy_support def test_scene(): - scene = window.Scene() - + # Scene size test npt.assert_equal(scene.size(), (0, 0)) - - # background color for scene (1, 0.5, 0) - # 0.001 added here to remove numerical errors when moving from float - # to int values + # Color background test + # Background color for scene (1, 0.5, 0). 0.001 added here to remove + # numerical errors when moving from float to int values bg_float = (1, 0.501, 0) - - # that will come in the image in the 0-255 uint scale + # That will come in the image in the 0-255 uint scale bg_color = tuple((np.round(255 * np.array(bg_float))).astype('uint8')) - scene.background(bg_float) - # window.show(scene) arr = window.snapshot(scene) - - report = window.analyze_snapshot(arr, - bg_color=bg_color, + report = window.analyze_snapshot(arr, bg_color=bg_color, colors=[bg_color, (0, 127, 0)]) npt.assert_equal(report.objects, 0) npt.assert_equal(report.colors_found, [True, False]) - + # Add actor to scene to test the remove actor function by analyzing a + # snapshot axes = actor.axes() scene.add(axes) - # window.show(scene) - arr = window.snapshot(scene) report = window.analyze_snapshot(arr, bg_color) npt.assert_equal(report.objects, 1) - + # Test remove actor function by analyzing a snapshot scene.rm(axes) arr = window.snapshot(scene) report = window.analyze_snapshot(arr, bg_color) npt.assert_equal(report.objects, 0) - + # Add actor to scene to test the remove all actors function by analyzing a + # snapshot scene.add(axes) arr = window.snapshot(scene) report = window.analyze_snapshot(arr, bg_color) npt.assert_equal(report.objects, 1) - + # Test remove all actors function by analyzing a snapshot scene.rm_all() arr = window.snapshot(scene) report = window.analyze_snapshot(arr, bg_color) npt.assert_equal(report.objects, 0) - + # Test change background color from scene by analyzing the scene ren2 = window.Scene(bg_float) ren2.background((0, 0, 0.)) - report = window.analyze_scene(ren2) npt.assert_equal(report.bg_color, (0, 0, 0)) - + # Add actor to scene to test the remove actor function by analyzing the + # scene ren2.add(axes) - report = window.analyze_scene(ren2) npt.assert_equal(report.actors, 1) - + # Test remove actor function by analyzing the scene ren2.rm(axes) report = window.analyze_scene(ren2) npt.assert_equal(report.actors, 0) - + # Test camera information retrieving with captured_output() as (out, err): scene.camera_info() npt.assert_equal(out.getvalue().strip(), @@ -81,6 +75,46 @@ def test_scene(): 'Focal Point (0.00, 0.00, 0.00)\n ' 'View Up (0.00, 1.00, 0.00)') npt.assert_equal(err.getvalue().strip(), '') + # Test skybox_tex + scene = window.Scene() + npt.assert_equal(scene.GetUseImageBasedLighting(), False) + npt.assert_equal(scene.GetAutomaticLightCreation(), 1) + npt.assert_equal(scene.GetSphericalHarmonics(), None) + npt.assert_equal(scene.GetEnvironmentTexture(), None) + test_tex = Texture() + scene = window.Scene(skybox_tex=test_tex) + npt.assert_equal(scene.GetUseImageBasedLighting(), True) + npt.assert_equal(scene.GetAutomaticLightCreation(), 0) + npt.assert_equal(scene.GetSphericalHarmonics(), None) + npt.assert_equal(scene.GetEnvironmentTexture(), test_tex) + # Test render_skybox + test_tex = Texture() + test_tex.CubeMapOn() + checker_arr = np.array([[1, 0], [0, 1]], dtype=np.uint8) * 255 + for i in range(6): + vtk_img = ImageData() + vtk_img.SetDimensions(2, 2, 1) + img_arr = np.zeros((2, 2, 3), dtype=np.uint8) + img_arr[:, :, i // 2] = checker_arr + vtk_arr = numpy_support.numpy_to_vtk(img_arr.reshape((-1, 3), + order='F')) + vtk_arr.SetName('Image') + img_point_data = vtk_img.GetPointData() + img_point_data.AddArray(vtk_arr) + img_point_data.SetActiveScalars('Image') + test_tex.SetInputDataObject(i, vtk_img) + scene = window.Scene(skybox_tex=test_tex, render_skybox=True) + ss = window.snapshot(scene) + npt.assert_array_equal(ss[75, 75, :], [0, 0, 255]) + npt.assert_array_equal(ss[75, 225, :], [0, 0, 0]) + scene.yaw(90) + ss = window.snapshot(scene) + npt.assert_array_equal(ss[75, 75, :], [255, 0, 0]) + npt.assert_array_equal(ss[75, 225, :], [0, 0, 0]) + scene.pitch(90) + ss = window.snapshot(scene) + npt.assert_array_equal(ss[75, 75, :], [0, 0, 0]) + npt.assert_array_equal(ss[75, 225, :], [0, 255, 0]) def test_active_camera(): diff --git a/fury/window.py b/fury/window.py index e79581528..607755f07 100644 --- a/fury/window.py +++ b/fury/window.py @@ -11,9 +11,10 @@ from fury.decorators import is_osx from fury.interactor import CustomInteractorStyle from fury.io import load_image, save_image -from fury.lib import (Renderer, Volume, Actor2D, InteractorEventRecorder, - InteractorStyleImage, InteractorStyleTrackballCamera, - RenderWindow, RenderWindowInteractor, RenderLargeImage, +from fury.lib import (VTK_9_PLUS, OpenGLRenderer, Skybox, Volume, Actor2D, + InteractorEventRecorder, InteractorStyleImage, + InteractorStyleTrackballCamera, RenderWindow, + RenderWindowInteractor, RenderLargeImage, WindowToImageFilter, Command, numpy_support, colors) from fury.utils import asbytes from fury.shaders.base import GL_NUMBERS as _GL @@ -23,7 +24,7 @@ basestring = str -class Scene(Renderer): +class Scene(OpenGLRenderer): """Your scene class. This is an important object that is responsible for preparing objects @@ -32,6 +33,24 @@ class Scene(Renderer): but also it provides access to all the functionality available in ``vtkRenderer`` if necessary. """ + def __init__(self, bg_color=(0, 0, 0), skybox_tex=None, + render_skybox=False): + self.skybox = skybox_tex + self.render_skybox = render_skybox + self.skybox_actor = None + if skybox_tex: + self.AutomaticLightCreationOff() + self.UseImageBasedLightingOn() + self.UseSphericalHarmonicsOff() + if VTK_9_PLUS: + self.SetEnvironmentTexture(skybox_tex) + else: + self.SetEnvironmentCubeMap(skybox_tex) + if render_skybox: + self.skybox_actor = Skybox() + self.skybox_actor.SetTexture(skybox_tex) + self.skybox_actor.GammaCorrectOn() + self.add(self.skybox_actor) def background(self, color): """Set a background color.""" From 6855b9c50c6926d5ce2eca39d25131abb82b5508 Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Tue, 25 Jan 2022 19:26:16 -0500 Subject: [PATCH 12/32] Fixed merge issues. --- fury/tests/test_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fury/tests/test_utils.py b/fury/tests/test_utils.py index b10521779..74c1ee6d9 100644 --- a/fury/tests/test_utils.py +++ b/fury/tests/test_utils.py @@ -18,7 +18,7 @@ from fury import actor, window, utils from fury.lib import (numpy_support, PolyData, PolyDataMapper2D, Points, CellArray, Polygon, Actor2D, DoubleArray, - UnsignedCharArray, VTK_DOUBLE, VTK_INT, VTK_FLOAT) + UnsignedCharArray, TextActor3D, VTK_DOUBLE, VTK_FLOAT) import fury.primitive as fp From e95a2df796be1ee47ea5626b58398a9ee5665a96 Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Tue, 25 Jan 2022 19:31:00 -0500 Subject: [PATCH 13/32] bg_color param renamed to background. --- fury/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fury/window.py b/fury/window.py index 607755f07..6c1f35abe 100644 --- a/fury/window.py +++ b/fury/window.py @@ -33,7 +33,7 @@ class Scene(OpenGLRenderer): but also it provides access to all the functionality available in ``vtkRenderer`` if necessary. """ - def __init__(self, bg_color=(0, 0, 0), skybox_tex=None, + def __init__(self, background=(0, 0, 0), skybox_tex=None, render_skybox=False): self.skybox = skybox_tex self.render_skybox = render_skybox From d7d8b3a8e745abfa61798fe90fe97cc5acf83f9b Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Wed, 26 Jan 2022 15:00:19 -0500 Subject: [PATCH 14/32] Added fetch_viz_cubemaps and read_viz_cubemap to fury.data.fetcher. --- docs/examples/viz_pbr_interactive.py | 30 +++--------- fury/data/__init__.py | 6 ++- fury/data/fetcher.py | 69 ++++++++++++++++++++++++---- 3 files changed, 70 insertions(+), 35 deletions(-) diff --git a/docs/examples/viz_pbr_interactive.py b/docs/examples/viz_pbr_interactive.py index 1ce50e3e2..d21475c6d 100644 --- a/docs/examples/viz_pbr_interactive.py +++ b/docs/examples/viz_pbr_interactive.py @@ -1,7 +1,6 @@ from fury import actor, material, window -from fury.data import fetch_viz_textures, read_viz_textures +from fury.data import fetch_viz_cubemaps, read_viz_cubemap from fury.lib import ImageFlip, ImageReader2Factory, Texture -from fury.utils import add_polydata_numeric_field, get_polydata_field import numpy as np import os @@ -29,17 +28,11 @@ def get_cubemap_texture(file_names, interpolate_on=True, mipmap_on=True): return texture -# TODO: Fetch skybox -# TODO: Create wrapper function with name order -texture_name = 'skybox' -cubemap_fns = [read_viz_textures(texture_name + '-px.jpg'), - read_viz_textures(texture_name + '-nx.jpg'), - read_viz_textures(texture_name + '-py.jpg'), - read_viz_textures(texture_name + '-ny.jpg'), - read_viz_textures(texture_name + '-pz.jpg'), - read_viz_textures(texture_name + '-nz.jpg')] +fetch_viz_cubemaps() -cubemap = get_cubemap_texture(cubemap_fns) +textures = read_viz_cubemap('skybox') + +cubemap = get_cubemap_texture(textures) scene = window.Scene(skybox_tex=cubemap, render_skybox=True) @@ -47,18 +40,7 @@ def get_cubemap_texture(file_names, interpolate_on=True, mipmap_on=True): #skybox.EdgeClampOn() sphere = actor.sphere([[0, 0, 0]], (.7, .7, .7), radii=2, theta=64, phi=64) -polydata = sphere.GetMapper().GetInput() - -# TODO: field_from_actor -print(get_polydata_field(polydata, 'Uses IBL')) - -# TODO: field_to_actor -field = scene.GetUseImageBasedLighting() -field_name = 'Uses IBL' -add_polydata_numeric_field(polydata, field_name, field) - -# TODO: field_from_actor -print(get_polydata_field(polydata, 'Uses IBL')) +material.manifest_pbr(sphere) scene.add(sphere) diff --git a/fury/data/__init__.py b/fury/data/__init__.py index b6858f908..710747620 100644 --- a/fury/data/__init__.py +++ b/fury/data/__init__.py @@ -2,7 +2,8 @@ from os.path import join as pjoin, dirname -from fury.data.fetcher import (fetch_viz_icons, read_viz_icons, +from fury.data.fetcher import (fetch_viz_cubemaps, read_viz_cubemap, + fetch_viz_icons, read_viz_icons, fetch_viz_wiki_nw, fetch_viz_textures, read_viz_textures, fetch_viz_models, read_viz_models, fetch_viz_dmri, @@ -10,7 +11,8 @@ DATA_DIR = pjoin(dirname(__file__), 'files') -__all__ = ['fetch_viz_icons', 'read_viz_icons', 'DATA_DIR', +__all__ = ['DATA_DIR', 'fetch_viz_cubemaps', 'read_viz_cubemap', + 'fetch_viz_icons', 'read_viz_icons', 'fetch_viz_textures', 'read_viz_textures', 'fetch_viz_wiki_nw', 'fetch_viz_models', 'read_viz_models', 'fetch_viz_dmri', diff --git a/fury/data/fetcher.py b/fury/data/fetcher.py index 07ac7fc2b..1f891ec72 100644 --- a/fury/data/fetcher.py +++ b/fury/data/fetcher.py @@ -3,6 +3,7 @@ import os import sys import contextlib +import warnings from os.path import join as pjoin from hashlib import sha256 @@ -263,6 +264,23 @@ def fetcher(): return fetcher +fetch_viz_cubemaps = _make_fetcher( + "fetch_viz_cubemaps", + pjoin(fury_home, "cubemaps"), + TEXTURE_DATA_URL, + ['skybox-nx.jpg', 'skybox-ny.jpg', 'skybox-nz.jpg', 'skybox-px.jpg', + 'skybox-py.jpg', 'skybox-pz.jpg'], + ['skybox-nx.jpg', 'skybox-ny.jpg', 'skybox-nz.jpg', 'skybox-px.jpg', + 'skybox-py.jpg', 'skybox-pz.jpg'], + ['12B1CE6C91AA3AAF258A8A5944DF739A6C1CC76E89D4D7119D1F795A30FC1BF2', + 'E18FE2206B63D3DF2C879F5E0B9937A61D99734B6C43AC288226C58D2418D23E', + '00DDDD1B715D5877AF2A74C014FF6E47891F07435B471D213CD0673A8C47F2B2', + 'BF20ACD6817C9E7073E485BBE2D2CE56DACFF73C021C2B613BA072BA2DF2B754', + '16F0D692AF0B80E46929D8D8A7E596123C76729CC5EB7DFD1C9184B115DD143A', + 'B850B5E882889DF26BE9289D7C25BA30524B37E56BC2075B968A83197AD977F3'], + doc="Download cube map textures for fury" +) + fetch_viz_icons = _make_fetcher( "fetch_viz_icons", pjoin(fury_home, "icons"), @@ -323,8 +341,6 @@ def fetcher(): ['1_earth_8k.jpg', '2_no_clouds_8k.jpg', '5_night_8k.jpg', 'earth.ppm', 'jupiter.jpg', 'masonry.bmp', - 'skybox-nx.jpg', 'skybox-ny.jpg', 'skybox-nz.jpg', - 'skybox-px.jpg', 'skybox-py.jpg', 'skybox-pz.jpg', 'moon_8k.jpg', '8k_mercury.jpg', '8k_venus_surface.jpg', '8k_mars.jpg', '8k_saturn.jpg', @@ -335,8 +351,6 @@ def fetcher(): ['1_earth_8k.jpg', '2_no_clouds_8k.jpg', '5_night_8k.jpg', 'earth.ppm', 'jupiter.jpg', 'masonry.bmp', - 'skybox-nx.jpg', 'skybox-ny.jpg', 'skybox-nz.jpg', - 'skybox-px.jpg', 'skybox-py.jpg', 'skybox-pz.jpg', 'moon-8k.jpg', '8k_mercury.jpg', '8k_venus_surface.jpg', '8k_mars.jpg', '8k_saturn.jpg', @@ -350,11 +364,6 @@ def fetcher(): '34CE9AD183D7C7B11E2F682D7EBB84C803E661BE09E01ADB887175AE60C58156', '5DF6A384E407BD0D5F18176B7DB96AAE1EEA3CFCFE570DDCE0D34B4F0E493668', '045E30B2ABFEAE6318C2CF955040C4A37E6DE595ACE809CE6766D397C0EE205D', - '12B1CE6C91AA3AAF258A8A5944DF739A6C1CC76E89D4D7119D1F795A30FC1BF2', - 'E18FE2206B63D3DF2C879F5E0B9937A61D99734B6C43AC288226C58D2418D23E', - 'BF20ACD6817C9E7073E485BBE2D2CE56DACFF73C021C2B613BA072BA2DF2B754', - '16F0D692AF0B80E46929D8D8A7E596123C76729CC5EB7DFD1C9184B115DD143A', - 'B850B5E882889DF26BE9289D7C25BA30524B37E56BC2075B968A83197AD977F3', '7397A6C2CE0348E148C66EBEFE078467DDB9D0370FF5E63434D0451477624839', '5C8BD885AE3571C6BA2CD34B3446B9C6D767E314BF0EE8C1D5C147CADD388FC3', '9BC21A50577ED8AC734CDA91058724C7A741C19427AA276224CE349351432C5B', @@ -370,6 +379,48 @@ def fetcher(): ) +def read_viz_cubemap(name, suffix_type=1, ext='.jpg'): + """Read specific cube map with specific suffix type and extension. + + Parameters + ---------- + name : str + suffix_type : int, optional + 0 for numeric suffix (e.g., skybox_0.jpg, skybox_1.jpg, etc.), 1 for + -p/nC encoding where C is either x, y or z (e.g., skybox-px.jpeg, + skybox-ny.jpeg, etc.), 2 for pos/negC where C is either x, y, z (e.g., + skybox_posx.png, skybox_negy.png, etc.), and 3 for position in the cube + map (e.g., skybox_right.jpg, skybox_front.jpg, etc). + ext : str, optional + Image type extension. (.jpg, .jpeg, .png, etc.). + + Returns + ------- + list of paths : list + List with the complete paths of the skybox textures. + + """ + # Set of commonly used cube map naming conventions and its associated + # indexing number. For a correct creation and display of the skybox, + # textures must be read in this order. + suffix_types = { + 0: ['0', '1', '2', '3', '4', '5'], + 1: ['-px', '-nx', '-py', '-ny', '-pz', '-nz'], + 2: ['posx', 'negx', 'posy', 'negy', 'posz', 'negz'], + 3: ['right', 'left', 'top', 'bottom', 'front', 'back'] + } + if suffix_type in suffix_types: + conv = suffix_types[suffix_type] + else: + warnings.warn('read_viz_cubemap(): Invalid suffix_type.') + return None + cubemap_fnames = [] + folder = pjoin(fury_home, 'cubemaps') + for dir_conv in conv: + cubemap_fnames.append(pjoin(folder, name + dir_conv + ext)) + return cubemap_fnames + + def read_viz_icons(style='icomoon', fname='infinity.png'): """Read specific icon from specific style. From a3e945af2363479edc086cdaf03a10e4a6d7be7d Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Wed, 26 Jan 2022 16:47:41 -0500 Subject: [PATCH 15/32] Added load_cubemap_texture function to I/O module. --- docs/examples/viz_pbr_interactive.py | 29 ++-------------------- fury/io.py | 37 +++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/docs/examples/viz_pbr_interactive.py b/docs/examples/viz_pbr_interactive.py index d21475c6d..bba1ba8d6 100644 --- a/docs/examples/viz_pbr_interactive.py +++ b/docs/examples/viz_pbr_interactive.py @@ -1,38 +1,13 @@ from fury import actor, material, window +from fury.io import load_cubemap_texture from fury.data import fetch_viz_cubemaps, read_viz_cubemap -from fury.lib import ImageFlip, ImageReader2Factory, Texture import numpy as np -import os - - -def get_cubemap_texture(file_names, interpolate_on=True, mipmap_on=True): - texture = Texture() - texture.CubeMapOn() - for idx, fn in enumerate(file_names): - if not os.path.isfile(fn): - raise FileNotFoundError(fn) - else: - # Read the images - reader_factory = ImageReader2Factory() - img_reader = reader_factory.CreateImageReader2(fn) - img_reader.SetFileName(fn) - - flip = ImageFlip() - flip.SetInputConnection(img_reader.GetOutputPort()) - flip.SetFilteredAxis(1) # flip y axis - texture.SetInputConnection(idx, flip.GetOutputPort(0)) - if interpolate_on: - texture.InterpolateOn() - if mipmap_on: - texture.MipmapOn() - return texture - fetch_viz_cubemaps() textures = read_viz_cubemap('skybox') -cubemap = get_cubemap_texture(textures) +cubemap = load_cubemap_texture(textures) scene = window.Scene(skybox_tex=cubemap, render_skybox=True) diff --git a/fury/io.py b/fury/io.py index 96ba9f087..635191d85 100644 --- a/fury/io.py +++ b/fury/io.py @@ -11,10 +11,45 @@ XMLPolyDataReader, PLYReader, STLReader, OBJReader, MNIObjectReader, PolyDataWriter, XMLPolyDataWriter, PLYWriter, STLWriter, - MNIObjectWriter) + MNIObjectWriter, ImageFlip, Texture) from fury.utils import set_input +def load_cubemap_texture(fnames, interpolate_on=True, mipmap_on=True): + """Load a cube map texture from a list of 6 images. + + Parameters + ---------- + fnames : list of strings + List of 6 filenames with png, bmp, jpeg or jpg extensions. + interpolate_on : bool, optional + mipmap_on : bool, optional + + Returns + ------- + output : vtkTexture + Cube map texture. + + """ + texture = Texture() + texture.CubeMapOn() + for idx, fn in enumerate(fnames): + if not os.path.isfile(fn): + raise FileNotFoundError(fn) + else: + # Read the images + vtk_img = load_image(fn, as_vtktype=True) + flip = ImageFlip() + flip.SetInputData(vtk_img) + flip.SetFilteredAxis(1) # flip y axis + texture.SetInputConnection(idx, flip.GetOutputPort(0)) + if interpolate_on: + texture.InterpolateOn() + if mipmap_on: + texture.MipmapOn() + return texture + + def load_image(filename, as_vtktype=False, use_pillow=True): """Load an image. From 212699ba8a2cfa4b7ff28356591bc7154300b792 Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Wed, 26 Jan 2022 19:00:20 -0500 Subject: [PATCH 16/32] Added dynamic demo. --- docs/examples/viz_pbr_interactive.py | 214 ++++++++++++++++++++++++++- fury/lib.py | 2 + fury/material.py | 43 ++++-- 3 files changed, 242 insertions(+), 17 deletions(-) diff --git a/docs/examples/viz_pbr_interactive.py b/docs/examples/viz_pbr_interactive.py index bba1ba8d6..63fbad3ad 100644 --- a/docs/examples/viz_pbr_interactive.py +++ b/docs/examples/viz_pbr_interactive.py @@ -1,7 +1,104 @@ -from fury import actor, material, window +from fury import actor, material, ui, window from fury.io import load_cubemap_texture from fury.data import fetch_viz_cubemaps, read_viz_cubemap -import numpy as np +from fury.utils import (normals_from_actor, tangents_to_actor, + tangents_from_direction_of_anisotropy) + + +def build_label(text, font_size=16, color=(1, 1, 1), bold=False, italic=False, + shadow=False): + label = ui.TextBlock2D() + label.message = text + label.font_size = font_size + label.font_family = 'Arial' + label.justification = 'left' + label.bold = bold + label.italic = italic + label.shadow = shadow + label.actor.GetTextProperty().SetBackgroundColor(0, 0, 0) + label.actor.GetTextProperty().SetBackgroundOpacity(0.0) + label.color = color + return label + + +def change_slice_metallic(slider): + global pbr_params, sphere + pbr_params['metallic'] = slider.value + sphere.GetProperty().SetMetallic(pbr_params['metallic']) + + +def change_slice_roughness(slider): + global pbr_params, sphere + pbr_params['roughness'] = slider.value + sphere.GetProperty().SetRoughness(pbr_params['roughness']) + + +def change_slice_anisotropy(slider): + global pbr_params, sphere + pbr_params['anisotropy'] = slider.value + sphere.GetProperty().SetAnisotropy(pbr_params['anisotropy']) + + +def change_slice_anisotropy_direction_x(slider): + global doa, normals, sphere + doa[0] = slider.value + tangents = tangents_from_direction_of_anisotropy(normals, doa) + tangents_to_actor(sphere, tangents) + + +def change_slice_anisotropy_direction_y(slider): + global doa, normals, sphere + doa[1] = slider.value + tangents = tangents_from_direction_of_anisotropy(normals, doa) + tangents_to_actor(sphere, tangents) + + +def change_slice_anisotropy_direction_z(slider): + global doa, normals, sphere + doa[2] = slider.value + tangents = tangents_from_direction_of_anisotropy(normals, doa) + tangents_to_actor(sphere, tangents) + + +def change_slice_anisotropy_rotation(slider): + global pbr_params, sphere + pbr_params['anisotropy_rotation'] = slider.value + sphere.GetProperty().SetAnisotropyRotation( + pbr_params['anisotropy_rotation']) + + +def change_slice_coat_strength(slider): + global pbr_params, sphere + pbr_params['coat_strength'] = slider.value + sphere.GetProperty().SetCoatStrength(pbr_params['coat_strength']) + + +def change_slice_coat_roughness(slider): + global pbr_params, sphere + pbr_params['coat_roughness'] = slider.value + sphere.GetProperty().SetCoatRoughness(pbr_params['coat_roughness']) + + +def change_slice_base_ior(slider): + global pbr_params, sphere + pbr_params['base_ior'] = slider.value + sphere.GetProperty().SetBaseIOR(pbr_params['base_ior']) + + +def change_slice_coat_ior(slider): + global pbr_params, sphere + pbr_params['coat_ior'] = slider.value + sphere.GetProperty().SetCoatIOR(pbr_params['coat_ior']) + + +def win_callback(obj, event): + global control_panel, size + if size != obj.GetSize(): + size_old = size + size = obj.GetSize() + size_change = [size[0] - size_old[0], 0] + control_panel.re_align(size_change) + fetch_viz_cubemaps() @@ -15,8 +112,117 @@ #skybox.EdgeClampOn() sphere = actor.sphere([[0, 0, 0]], (.7, .7, .7), radii=2, theta=64, phi=64) -material.manifest_pbr(sphere) + +doa = [0, 1, .5] + +normals = normals_from_actor(sphere) +tangents = tangents_from_direction_of_anisotropy(normals, doa) +tangents_to_actor(sphere, tangents) + +pbr_params = material.manifest_pbr(sphere) scene.add(sphere) -window.show(scene) +show_m = window.ShowManager(scene=scene, reset_camera=False, + order_transparent=True) +show_m.initialize() + +control_panel = ui.Panel2D( + (400, 500), position=(-105, 5), color=(.25, .25, .25), opacity=.75, + align='right') + +slider_label_metallic = build_label('Metallic') +slider_label_roughness = build_label('Roughness') +slider_label_anisotropy = build_label('Anisotropy') +slider_label_anisotropy_rotation = build_label('Anisotropy Rotation') +slider_label_anisotropy_direction_x = build_label('Anisotropy Direction X') +slider_label_anisotropy_direction_y = build_label('Anisotropy Direction Y') +slider_label_anisotropy_direction_z = build_label('Anisotropy Direction Z') +slider_label_coat_strength = build_label('Coat Strength') +slider_label_coat_roughness = build_label('Coat Roughness') +slider_label_base_ior = build_label('Base IoR') +slider_label_coat_ior = build_label('Coat IoR') + +control_panel.add_element(slider_label_metallic, (.01, .95)) +control_panel.add_element(slider_label_roughness, (.01, .86)) +control_panel.add_element(slider_label_anisotropy, (.01, .77)) +control_panel.add_element(slider_label_anisotropy_rotation, (.01, .68)) +control_panel.add_element(slider_label_anisotropy_direction_x, (.01, .59)) +control_panel.add_element(slider_label_anisotropy_direction_y, (.01, .5)) +control_panel.add_element(slider_label_anisotropy_direction_z, (.01, .41)) +control_panel.add_element(slider_label_coat_strength, (.01, .32)) +control_panel.add_element(slider_label_coat_roughness, (.01, .23)) +control_panel.add_element(slider_label_base_ior, (.01, .14)) +control_panel.add_element(slider_label_coat_ior, (.01, .05)) + +slider_slice_metallic = ui.LineSlider2D( + initial_value=pbr_params['metallic'], max_value=1, length=195, + text_template='{value:.1f}') +slider_slice_roughness = ui.LineSlider2D( + initial_value=pbr_params['roughness'], max_value=1, length=195, + text_template='{value:.1f}') +slider_slice_anisotropy = ui.LineSlider2D( + initial_value=pbr_params['anisotropy'], max_value=1, length=195, + text_template='{value:.1f}') +slider_slice_anisotropy_rotation = ui.LineSlider2D( + initial_value=pbr_params['anisotropy_rotation'], max_value=1, length=195, + text_template='{value:.1f}') + +slider_slice_anisotropy_direction_x = ui.LineSlider2D( + initial_value=doa[0], min_value=-1, max_value=1, length=195, + text_template='{value:.1f}') +slider_slice_anisotropy_direction_y = ui.LineSlider2D( + initial_value=doa[1], min_value=-1, max_value=1, length=195, + text_template='{value:.1f}') +slider_slice_anisotropy_direction_z = ui.LineSlider2D( + initial_value=doa[2], min_value=-1, max_value=1, length=195, + text_template='{value:.1f}') + +slider_slice_coat_strength = ui.LineSlider2D( + initial_value=pbr_params['coat_strength'], max_value=1, length=195, + text_template='{value:.1f}') +slider_slice_coat_roughness = ui.LineSlider2D( + initial_value=pbr_params['coat_roughness'], max_value=1, length=195, + text_template='{value:.1f}') + +slider_slice_base_ior = ui.LineSlider2D( + initial_value=pbr_params['base_ior'], min_value=1, max_value=2.3, + length=195, text_template='{value:.02f}') +slider_slice_coat_ior = ui.LineSlider2D( + initial_value=pbr_params['coat_ior'], min_value=1, max_value=2.3, + length=195, text_template='{value:.02f}') + +slider_slice_metallic.on_change = change_slice_metallic +slider_slice_roughness.on_change = change_slice_roughness +slider_slice_anisotropy.on_change = change_slice_anisotropy +slider_slice_anisotropy_rotation.on_change = change_slice_anisotropy_rotation +slider_slice_anisotropy_direction_x.on_change = ( + change_slice_anisotropy_direction_x) +slider_slice_anisotropy_direction_y.on_change = ( + change_slice_anisotropy_direction_y) +slider_slice_anisotropy_direction_z.on_change = ( + change_slice_anisotropy_direction_z) +slider_slice_coat_strength.on_change = change_slice_coat_strength +slider_slice_coat_roughness.on_change = change_slice_coat_roughness +slider_slice_base_ior.on_change = change_slice_base_ior +slider_slice_coat_ior.on_change = change_slice_coat_ior + +control_panel.add_element(slider_slice_metallic, (.44, .95)) +control_panel.add_element(slider_slice_roughness, (.44, .86)) +control_panel.add_element(slider_slice_anisotropy, (.44, .77)) +control_panel.add_element(slider_slice_anisotropy_rotation, (.44, .68)) +control_panel.add_element(slider_slice_anisotropy_direction_x, (.44, .59)) +control_panel.add_element(slider_slice_anisotropy_direction_y, (.44, .5)) +control_panel.add_element(slider_slice_anisotropy_direction_z, (.44, .41)) +control_panel.add_element(slider_slice_coat_strength, (.44, .32)) +control_panel.add_element(slider_slice_coat_roughness, (.44, .23)) +control_panel.add_element(slider_slice_base_ior, (.44, .14)) +control_panel.add_element(slider_slice_coat_ior, (.44, .05)) + +scene.add(control_panel) + +size = scene.GetSize() + +show_m.add_window_callback(win_callback) + +show_m.start() diff --git a/fury/lib.py b/fury/lib.py index 3b223454c..72395fce9 100644 --- a/fury/lib.py +++ b/fury/lib.py @@ -31,6 +31,8 @@ VTK_9_PLUS = ccvtk.vtkVersion.GetVTKMajorVersion() >= 9 +VTK_9_1_PLUS = (ccvtk.vtkVersion.GetVTKMajorVersion() >= 9 and + ccvtk.vtkVersion.GetVTKMinorVersion() >= 1) VTK_VERSION = ccvtk.vtkVersion.GetVTKVersion() ############################################################## diff --git a/fury/material.py b/fury/material.py index b451bd0d6..18fe8c20e 100644 --- a/fury/material.py +++ b/fury/material.py @@ -2,7 +2,7 @@ from fury.shaders import add_shader_callback, load, shader_to_actor -from fury.lib import VTK_9_PLUS, VTK_OBJECT, calldata_type +from fury.lib import VTK_9_PLUS, VTK_9_1_PLUS, VTK_OBJECT, calldata_type def manifest_pbr(actor, metallic=0, roughness=.5, anisotropy=0, @@ -40,31 +40,48 @@ def manifest_pbr(actor, metallic=0, roughness=.5, anisotropy=0, """ if not VTK_9_PLUS: - warnings.warn('Your PBR effect cannot be apply due to VTK version. ' + warnings.warn('Your PBR effect cannot be applied due to VTK version. ' 'Please upgrade your VTK version (should be >= 9.0.0).') - return + return None try: prop = actor.GetProperty() try: prop.SetInterpolationToPBR() - prop.SetMetallic(metallic) - prop.SetRoughness(roughness) - prop.SetAnisotropy(anisotropy) - prop.SetAnisotropyRotation(anisotropy_rotation) - prop.SetCoatStrength(coat_strength) - prop.SetCoatRoughness(coat_roughness) - prop.SetBaseIOR(base_ior) - prop.SetCoatIOR(coat_ior) + + pbr_params = {'metallic': metallic, 'roughness': roughness} + + prop.SetMetallic(pbr_params['metallic']) + prop.SetRoughness(pbr_params['roughness']) + if VTK_9_1_PLUS: + pbr_params['anisotropy'] = anisotropy + pbr_params['anisotropy_rotation'] = anisotropy_rotation + pbr_params['coat_strength'] = coat_strength + pbr_params['coat_roughness'] = coat_roughness + pbr_params['base_ior'] = base_ior + pbr_params['coat_ior'] = coat_ior + + prop.SetAnisotropy(pbr_params['anisotropy']) + prop.SetAnisotropyRotation(pbr_params['anisotropy_rotation']) + prop.SetCoatStrength(pbr_params['coat_strength']) + prop.SetCoatRoughness(pbr_params['coat_roughness']) + prop.SetBaseIOR(pbr_params['base_ior']) + prop.SetCoatIOR(pbr_params['coat_ior']) + else: + warnings.warn( + 'Anisotropy and Clear coat based PBR effects cannot be ' + 'applied due to VTK version. Please upgrade your VTK ' + 'version (should be >= 9.1.0).') + return pbr_params except AttributeError: warnings.warn( 'PBR interpolation cannot be applied to this actor. The ' 'material will not be applied.') - return + return None except AttributeError: warnings.warn('Actor does not have the attribute property. This ' 'material will not be applied.') - return + return None def manifest_principled(actor, subsurface=0, subsurface_color=[0, 0, 0], From 0d032a463831d82291740d48b06215c4a6f99e96 Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Thu, 27 Jan 2022 09:25:12 -0500 Subject: [PATCH 17/32] Improved load_cubemap_texture function and added test. --- fury/io.py | 13 ++++++++----- fury/tests/test_io.py | 22 ++++++++++++++++++++-- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/fury/io.py b/fury/io.py index 635191d85..9bededbeb 100644 --- a/fury/io.py +++ b/fury/io.py @@ -21,7 +21,7 @@ def load_cubemap_texture(fnames, interpolate_on=True, mipmap_on=True): Parameters ---------- fnames : list of strings - List of 6 filenames with png, bmp, jpeg or jpg extensions. + List of 6 filenames with bmp, jpg, jpeg, png, tif or tiff extensions. interpolate_on : bool, optional mipmap_on : bool, optional @@ -39,10 +39,13 @@ def load_cubemap_texture(fnames, interpolate_on=True, mipmap_on=True): else: # Read the images vtk_img = load_image(fn, as_vtktype=True) - flip = ImageFlip() - flip.SetInputData(vtk_img) - flip.SetFilteredAxis(1) # flip y axis - texture.SetInputConnection(idx, flip.GetOutputPort(0)) + # Flip the image horizontally + img_flip = ImageFlip() + img_flip.SetInputData(vtk_img) + img_flip.SetFilteredAxis(1) # flip y axis + img_flip.Update() + # Add the image to the cube map + texture.SetInputDataObject(idx, img_flip.GetOutput()) if interpolate_on: texture.InterpolateOn() if mipmap_on: diff --git a/fury/tests/test_io.py b/fury/tests/test_io.py index be2785ae4..d35eb221b 100644 --- a/fury/tests/test_io.py +++ b/fury/tests/test_io.py @@ -6,8 +6,8 @@ import pytest from fury.decorators import skip_osx -from fury.io import (load_polydata, save_polydata, load_image, save_image, - load_sprite_sheet) +from fury.io import (load_cubemap_texture, load_polydata, save_polydata, + load_image, save_image, load_sprite_sheet) from fury.lib import numpy_support, PolyData, ImageData from fury.utils import numpy_to_vtk_points from fury.testing import assert_greater @@ -158,6 +158,24 @@ def test_pillow(): npt.assert_equal(data.dtype, data2.dtype) +def test_load_cubemap_texture(): + l_ext = ['jpg', 'jpeg', 'png', 'bmp', 'tif', 'tiff'] + for ext in l_ext: + with InTemporaryDirectory() as odir: + data = np.random.randint(0, 255, size=(50, 50, 3), dtype=np.uint8) + fname_path = pjoin(odir, f'test.{ext}') + save_image(data, fname_path) + + fnames = [fname_path] * 6 + texture = load_cubemap_texture(fnames) + npt.assert_equal(texture.GetCubeMap(), True) + npt.assert_equal(texture.GetMipmap(), True) + npt.assert_equal(texture.GetInterpolate(), 1) + npt.assert_equal(texture.GetNumberOfInputPorts(), 6) + npt.assert_equal(texture.GetInputDataObject(0, 0).GetDimensions(), + (50, 50, 1)) + + def test_load_sprite_sheet(): sprite_URL = 'https://raw.githubusercontent.com/'\ 'antrikshmisri/DATA/master/fury/0yKFTBQ.png' From d2b7a8250615c863b99c938f9fc3b95c41a741eb Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Thu, 27 Jan 2022 10:05:34 -0500 Subject: [PATCH 18/32] Addressed PEP8 issues. --- docs/examples/viz_pbr_interactive.py | 7 ++----- docs/tutorials/03_shaders/viz_pbr_spheres.py | 12 ++++++------ 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/docs/examples/viz_pbr_interactive.py b/docs/examples/viz_pbr_interactive.py index 63fbad3ad..f2b84aa3c 100644 --- a/docs/examples/viz_pbr_interactive.py +++ b/docs/examples/viz_pbr_interactive.py @@ -108,9 +108,6 @@ def win_callback(obj, event): scene = window.Scene(skybox_tex=cubemap, render_skybox=True) -#skybox.RepeatOff() -#skybox.EdgeClampOn() - sphere = actor.sphere([[0, 0, 0]], (.7, .7, .7), radii=2, theta=64, phi=64) doa = [0, 1, .5] @@ -123,12 +120,12 @@ def win_callback(obj, event): scene.add(sphere) -show_m = window.ShowManager(scene=scene, reset_camera=False, +show_m = window.ShowManager(scene=scene, size=(1920, 1080), reset_camera=False, order_transparent=True) show_m.initialize() control_panel = ui.Panel2D( - (400, 500), position=(-105, 5), color=(.25, .25, .25), opacity=.75, + (400, 500), position=(5, 5), color=(.25, .25, .25), opacity=.75, align='right') slider_label_metallic = build_label('Metallic') diff --git a/docs/tutorials/03_shaders/viz_pbr_spheres.py b/docs/tutorials/03_shaders/viz_pbr_spheres.py index 6b3ea24fa..78257047c 100644 --- a/docs/tutorials/03_shaders/viz_pbr_spheres.py +++ b/docs/tutorials/03_shaders/viz_pbr_spheres.py @@ -43,7 +43,7 @@ ] """ -Now we can start to add our actors to the scene and see how different values of +Now we can start to add our actors to the scene and see how different values of the parameters produce interesting effects. """ @@ -62,7 +62,7 @@ scene.add(sphere) """ -For interpretability purposes we will add some labels to guide us through our +For interpretability purposes we will add some labels to guide us through our visualization. """ @@ -82,10 +82,10 @@ scene.add(label) """ -Some parameters of this material have their values constrained to be between 1 -and 2.3. These parameters are the Base Index of Refraction (IOR) and the Clear -coat Index of Refraction (IOR). Therefore, we will interpolate some values -within this range and see how they affect the rendering. +Some parameters of this material have their values constrained to be between 1 +and 2.3. These parameters are the Base Index of Refraction (IOR) and the Clear +coat Index of Refraction (IOR). Therefore, we will interpolate some values +within this range and see how they affect the rendering. """ iors = np.round(np.linspace(1, 2.3, num=11), decimals=2) From acbfb2a0732425e54920d65e2869df01738a31ca Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Thu, 27 Jan 2022 13:18:17 -0500 Subject: [PATCH 19/32] Improved descriptions of both demo and tutorial. --- docs/examples/viz_pbr_interactive.py | 144 +++++++++++++++++-- docs/tutorials/03_shaders/viz_pbr_spheres.py | 2 +- 2 files changed, 137 insertions(+), 9 deletions(-) diff --git a/docs/examples/viz_pbr_interactive.py b/docs/examples/viz_pbr_interactive.py index f2b84aa3c..f011a8893 100644 --- a/docs/examples/viz_pbr_interactive.py +++ b/docs/examples/viz_pbr_interactive.py @@ -1,10 +1,26 @@ +""" +=============================================== +Interactive PBR demo +=============================================== + +This is a demonstration of how Physically-Based Rendering (PBR) can be used to +simulate different materials. + +Let's start by importing the necessary modules: +""" + from fury import actor, material, ui, window -from fury.io import load_cubemap_texture from fury.data import fetch_viz_cubemaps, read_viz_cubemap +from fury.io import load_cubemap_texture from fury.utils import (normals_from_actor, tangents_to_actor, tangents_from_direction_of_anisotropy) +""" +This function will help us to define the appearance of our labels. +""" + + def build_label(text, font_size=16, color=(1, 1, 1), bold=False, italic=False, shadow=False): label = ui.TextBlock2D() @@ -21,6 +37,11 @@ def build_label(text, font_size=16, color=(1, 1, 1), bold=False, italic=False, return label +""" +The following functions will help us to manage the sliders events. +""" + + def change_slice_metallic(slider): global pbr_params, sphere pbr_params['metallic'] = slider.value @@ -91,6 +112,12 @@ def change_slice_coat_ior(slider): sphere.GetProperty().SetCoatIOR(pbr_params['coat_ior']) +""" +Last, but not least, we define the following function to help us to reposition +the UI elements every time we resize the window. +""" + + def win_callback(obj, event): global control_panel, size if size != obj.GetSize(): @@ -100,34 +127,94 @@ def win_callback(obj, event): control_panel.re_align(size_change) +""" +Let's fetch a skybox texture from the FURY data repository. +""" + fetch_viz_cubemaps() +""" +The following function returns the full path of the 6 images composing the +skybox. +""" + textures = read_viz_cubemap('skybox') +""" +Now that we have the location of the textures, let's load them and create a +Cube Map Texture object. +""" + cubemap = load_cubemap_texture(textures) +""" +The Scene object in FURY can handle cube map textures and extract light +information from them, so it can be used to create more plausible materials +interactions. The ``skybox_tex`` parameter is the one performing the previously +described process. On the other hand, the ``render_skybox`` parameter toggles +the rendering of the skybox. +""" + scene = window.Scene(skybox_tex=cubemap, render_skybox=True) +""" +With the scene created, we can then populate it. In this demo we will only add +a sphere actor. +""" + sphere = actor.sphere([[0, 0, 0]], (.7, .7, .7), radii=2, theta=64, phi=64) +""" +The direction of anisotropy (DoA) defines the direction at which all the +tangents of our actor are pointing. +""" + doa = [0, 1, .5] +""" +The following process gets the normals of the actor and computes the tangents +that are aligned to the provided DoA. Then it registers those tangents to the +actor. +""" + normals = normals_from_actor(sphere) tangents = tangents_from_direction_of_anisotropy(normals, doa) tangents_to_actor(sphere, tangents) +""" +With the tangents computed and in place, we have all the elements needed to +add some material properties to the actor. +""" + pbr_params = material.manifest_pbr(sphere) +""" +Our actor is now ready to be added to the scene. +""" + scene.add(sphere) +""" +Let's setup now the window and the UI. +""" + show_m = window.ShowManager(scene=scene, size=(1920, 1080), reset_camera=False, order_transparent=True) show_m.initialize() +""" +We will create one single panel with all of our labels and sliders. +""" + control_panel = ui.Panel2D( (400, 500), position=(5, 5), color=(.25, .25, .25), opacity=.75, align='right') +""" +By using our previously defined function, we can easily create all the labels +we need for this demo. And then add them to the panel. +""" + slider_label_metallic = build_label('Metallic') slider_label_roughness = build_label('Roughness') slider_label_anisotropy = build_label('Anisotropy') @@ -152,6 +239,10 @@ def win_callback(obj, event): control_panel.add_element(slider_label_base_ior, (.01, .14)) control_panel.add_element(slider_label_coat_ior, (.01, .05)) +""" +Our sliders are created and added to the panel in the following way. +""" + slider_slice_metallic = ui.LineSlider2D( initial_value=pbr_params['metallic'], max_value=1, length=195, text_template='{value:.1f}') @@ -164,6 +255,18 @@ def win_callback(obj, event): slider_slice_anisotropy_rotation = ui.LineSlider2D( initial_value=pbr_params['anisotropy_rotation'], max_value=1, length=195, text_template='{value:.1f}') +slider_slice_coat_strength = ui.LineSlider2D( + initial_value=pbr_params['coat_strength'], max_value=1, length=195, + text_template='{value:.1f}') +slider_slice_coat_roughness = ui.LineSlider2D( + initial_value=pbr_params['coat_roughness'], max_value=1, length=195, + text_template='{value:.1f}') + +""" +Notice that we are defining a range of [-1, 1] for the DoA. This is because +within that range we cover all the possible 3D directions needed to align the +tangents. +""" slider_slice_anisotropy_direction_x = ui.LineSlider2D( initial_value=doa[0], min_value=-1, max_value=1, length=195, @@ -175,12 +278,11 @@ def win_callback(obj, event): initial_value=doa[2], min_value=-1, max_value=1, length=195, text_template='{value:.1f}') -slider_slice_coat_strength = ui.LineSlider2D( - initial_value=pbr_params['coat_strength'], max_value=1, length=195, - text_template='{value:.1f}') -slider_slice_coat_roughness = ui.LineSlider2D( - initial_value=pbr_params['coat_roughness'], max_value=1, length=195, - text_template='{value:.1f}') +""" +Another special case are the Index of Refraction (IoR) sliders. In these cases, +the values are defined in the range [1, 2.3] according to the documentation of +the material. +""" slider_slice_base_ior = ui.LineSlider2D( initial_value=pbr_params['base_ior'], min_value=1, max_value=2.3, @@ -189,6 +291,10 @@ def win_callback(obj, event): initial_value=pbr_params['coat_ior'], min_value=1, max_value=2.3, length=195, text_template='{value:.02f}') +""" +Let's add the event handlers functions to the corresponding sliders. +""" + slider_slice_metallic.on_change = change_slice_metallic slider_slice_roughness.on_change = change_slice_roughness slider_slice_anisotropy.on_change = change_slice_anisotropy @@ -204,6 +310,10 @@ def win_callback(obj, event): slider_slice_base_ior.on_change = change_slice_base_ior slider_slice_coat_ior.on_change = change_slice_coat_ior +""" +And then add the sliders to the panel. +""" + control_panel.add_element(slider_slice_metallic, (.44, .95)) control_panel.add_element(slider_slice_roughness, (.44, .86)) control_panel.add_element(slider_slice_anisotropy, (.44, .77)) @@ -216,10 +326,28 @@ def win_callback(obj, event): control_panel.add_element(slider_slice_base_ior, (.44, .14)) control_panel.add_element(slider_slice_coat_ior, (.44, .05)) +""" +Consequently, we add the panel to the scene. +""" + scene.add(control_panel) +""" +Previously we defined a function to help us when we resize the window, so let's +capture the current size and add our helper function as a `window_callback` to +the window. +""" + size = scene.GetSize() show_m.add_window_callback(win_callback) -show_m.start() +""" +Finally, let's visualize our demo. +""" + +interactive = True +if interactive: + show_m.start() + +#window.record(scene, size=(1920, 1080), out_path="viz_pbr_interactive.png") diff --git a/docs/tutorials/03_shaders/viz_pbr_spheres.py b/docs/tutorials/03_shaders/viz_pbr_spheres.py index 78257047c..07dd63966 100644 --- a/docs/tutorials/03_shaders/viz_pbr_spheres.py +++ b/docs/tutorials/03_shaders/viz_pbr_spheres.py @@ -126,7 +126,7 @@ scene.add(label) """ -Finally, let's visualize our demo. +Finally, let's visualize our tutorial. """ interactive = False From 73798780b4d7f522352eddf54fc3f1d28bdd10eb Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Thu, 27 Jan 2022 13:21:40 -0500 Subject: [PATCH 20/32] Fixed PEP8 issues. --- docs/examples/viz_pbr_interactive.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/examples/viz_pbr_interactive.py b/docs/examples/viz_pbr_interactive.py index f011a8893..154e1b5d1 100644 --- a/docs/examples/viz_pbr_interactive.py +++ b/docs/examples/viz_pbr_interactive.py @@ -114,7 +114,7 @@ def change_slice_coat_ior(slider): """ Last, but not least, we define the following function to help us to reposition -the UI elements every time we resize the window. +the UI elements every time we resize the window. """ @@ -183,7 +183,7 @@ def win_callback(obj, event): """ With the tangents computed and in place, we have all the elements needed to -add some material properties to the actor. +add some material properties to the actor. """ pbr_params = material.manifest_pbr(sphere) @@ -346,8 +346,8 @@ def win_callback(obj, event): Finally, let's visualize our demo. """ -interactive = True +interactive = False if interactive: show_m.start() -#window.record(scene, size=(1920, 1080), out_path="viz_pbr_interactive.png") +window.record(scene, size=(1920, 1080), out_path="viz_pbr_interactive.png") From 03c04ffa8887146a1cca3b7f23603db9431011cd Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Thu, 27 Jan 2022 14:05:48 -0500 Subject: [PATCH 21/32] Added CUBEMAP_DATA_URL. --- fury/data/fetcher.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fury/data/fetcher.py b/fury/data/fetcher.py index 1f891ec72..3484ccdd6 100644 --- a/fury/data/fetcher.py +++ b/fury/data/fetcher.py @@ -24,6 +24,9 @@ UW_RW_URL = \ "https://digital.lib.washington.edu/researchworks/bitstream/handle/" +CUBEMAP_DATA_URL = \ + "https://raw.githubusercontent.com/fury-gl/fury-data/master/cubemaps/" + FURY_DATA_URL = \ "https://raw.githubusercontent.com/fury-gl/fury-data/master/examples/" @@ -267,7 +270,7 @@ def fetcher(): fetch_viz_cubemaps = _make_fetcher( "fetch_viz_cubemaps", pjoin(fury_home, "cubemaps"), - TEXTURE_DATA_URL, + CUBEMAP_DATA_URL, ['skybox-nx.jpg', 'skybox-ny.jpg', 'skybox-nz.jpg', 'skybox-px.jpg', 'skybox-py.jpg', 'skybox-pz.jpg'], ['skybox-nx.jpg', 'skybox-ny.jpg', 'skybox-nz.jpg', 'skybox-px.jpg', From d250b9af79586db7441b35632f25791e1b4f1622 Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Thu, 27 Jan 2022 14:06:58 -0500 Subject: [PATCH 22/32] Replaced VTK's numpy_support with FURY's numpy_support. --- fury/tests/test_window.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fury/tests/test_window.py b/fury/tests/test_window.py index c1ecb8d8e..cd9141dbc 100644 --- a/fury/tests/test_window.py +++ b/fury/tests/test_window.py @@ -6,11 +6,10 @@ import numpy.testing as npt import pytest from fury import actor, window, io -from fury.lib import ImageData, Texture +from fury.lib import ImageData, Texture, numpy_support from fury.testing import captured_output, assert_less_equal, assert_greater from fury.decorators import skip_osx, skip_win from fury import shaders -from vtk.util import numpy_support def test_scene(): From 1cc0458ed132929595940d0bac49d26381b9d715 Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Thu, 27 Jan 2022 14:07:31 -0500 Subject: [PATCH 23/32] Removed VTK_9_X_VERSION checks. --- fury/material.py | 32 ++++++++++++-------------------- fury/window.py | 7 ++----- 2 files changed, 14 insertions(+), 25 deletions(-) diff --git a/fury/material.py b/fury/material.py index 94c24c0ae..54744a79d 100644 --- a/fury/material.py +++ b/fury/material.py @@ -44,29 +44,21 @@ def manifest_pbr(actor, metallic=0, roughness=.5, anisotropy=0, try: prop.SetInterpolationToPBR() - pbr_params = {'metallic': metallic, 'roughness': roughness} + pbr_params = {'metallic': metallic, 'roughness': roughness, + 'anisotropy': anisotropy, + 'anisotropy_rotation': anisotropy_rotation, + 'coat_strength': coat_strength, + 'coat_roughness': coat_roughness, + 'base_ior': base_ior, 'coat_ior': coat_ior} prop.SetMetallic(pbr_params['metallic']) prop.SetRoughness(pbr_params['roughness']) - if VTK_9_1_PLUS: - pbr_params['anisotropy'] = anisotropy - pbr_params['anisotropy_rotation'] = anisotropy_rotation - pbr_params['coat_strength'] = coat_strength - pbr_params['coat_roughness'] = coat_roughness - pbr_params['base_ior'] = base_ior - pbr_params['coat_ior'] = coat_ior - - prop.SetAnisotropy(pbr_params['anisotropy']) - prop.SetAnisotropyRotation(pbr_params['anisotropy_rotation']) - prop.SetCoatStrength(pbr_params['coat_strength']) - prop.SetCoatRoughness(pbr_params['coat_roughness']) - prop.SetBaseIOR(pbr_params['base_ior']) - prop.SetCoatIOR(pbr_params['coat_ior']) - else: - warnings.warn( - 'Anisotropy and Clear coat based PBR effects cannot be ' - 'applied due to VTK version. Please upgrade your VTK ' - 'version (should be >= 9.1.0).') + prop.SetAnisotropy(pbr_params['anisotropy']) + prop.SetAnisotropyRotation(pbr_params['anisotropy_rotation']) + prop.SetCoatStrength(pbr_params['coat_strength']) + prop.SetCoatRoughness(pbr_params['coat_roughness']) + prop.SetBaseIOR(pbr_params['base_ior']) + prop.SetCoatIOR(pbr_params['coat_ior']) return pbr_params except AttributeError: warnings.warn( diff --git a/fury/window.py b/fury/window.py index 6c1f35abe..d0f333b6e 100644 --- a/fury/window.py +++ b/fury/window.py @@ -11,7 +11,7 @@ from fury.decorators import is_osx from fury.interactor import CustomInteractorStyle from fury.io import load_image, save_image -from fury.lib import (VTK_9_PLUS, OpenGLRenderer, Skybox, Volume, Actor2D, +from fury.lib import (OpenGLRenderer, Skybox, Volume, Actor2D, InteractorEventRecorder, InteractorStyleImage, InteractorStyleTrackballCamera, RenderWindow, RenderWindowInteractor, RenderLargeImage, @@ -42,10 +42,7 @@ def __init__(self, background=(0, 0, 0), skybox_tex=None, self.AutomaticLightCreationOff() self.UseImageBasedLightingOn() self.UseSphericalHarmonicsOff() - if VTK_9_PLUS: - self.SetEnvironmentTexture(skybox_tex) - else: - self.SetEnvironmentCubeMap(skybox_tex) + self.SetEnvironmentTexture(skybox_tex) if render_skybox: self.skybox_actor = Skybox() self.skybox_actor.SetTexture(skybox_tex) From 2049d318f820fcf1a202f90aa9e890f319832dd9 Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Thu, 27 Jan 2022 14:32:22 -0500 Subject: [PATCH 24/32] Improved readability of viz_pbr_spheres tutorial. --- docs/tutorials/03_shaders/viz_pbr_spheres.py | 51 +++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/docs/tutorials/03_shaders/viz_pbr_spheres.py b/docs/tutorials/03_shaders/viz_pbr_spheres.py index 07dd63966..4d35baa79 100644 --- a/docs/tutorials/03_shaders/viz_pbr_spheres.py +++ b/docs/tutorials/03_shaders/viz_pbr_spheres.py @@ -44,21 +44,25 @@ """ Now we can start to add our actors to the scene and see how different values of -the parameters produce interesting effects. +the parameters produce interesting effects. For the purpose of this tutorial, +we will see the effect of 11 different values of each parameter. """ -for i in range(6): +num_values = 11 + +for i, mp in enumerate(material_params): + color = mp[0] + params = mp[1] center = [[0, -5 * i, 0]] - for j in range(11): + for j in range(num_values): center[0][0] = -25 + 5 * j - sphere = actor.sphere(center, material_params[i][0], radii=2, theta=32, - phi=32) + sphere = actor.sphere(center, color, radii=2, theta=32, phi=32) normals = normals_from_actor(sphere) tangents = tangents_from_direction_of_anisotropy(normals, (0, 1, .5)) tangents_to_actor(sphere, tangents) - keys = list(material_params[i][1]) - material_params[i][1][keys[0]] = np.round(0.1 * j, decimals=1) - material.manifest_pbr(sphere, **material_params[i][1]) + keys = list(params) + params[keys[0]] = np.round(0.1 * j, decimals=1) + material.manifest_pbr(sphere, **params) scene.add(sphere) """ @@ -69,13 +73,12 @@ labels = ['Metallic', 'Roughness', 'Anisotropy', 'Anisotropy Rotation', 'Coat Strength', 'Coat Roughness'] -for i in range(6): +for i, l in enumerate(labels): pos = [-40, -5 * i, 0] - label = actor.vector_text(labels[i], pos=pos, scale=(.8, .8, .8), - color=(0, 0, 0)) + label = actor.vector_text(l, pos=pos, scale=(.8, .8, .8), color=(0, 0, 0)) scene.add(label) -for j in range(11): +for j in range(num_values): pos = [-26 + 5 * j, 3, 0] label = actor.vector_text(str(np.round(j * 0.1, decimals=1)), pos=pos, scale=(.8, .8, .8), color=(0, 0, 0)) @@ -88,7 +91,7 @@ within this range and see how they affect the rendering. """ -iors = np.round(np.linspace(1, 2.3, num=11), decimals=2) +iors = np.round(np.linspace(1, 2.3, num=num_values), decimals=2) ior_params = [ [(0, 1, 1), {'base_ior': iors[0], 'roughness': 0}], @@ -96,15 +99,16 @@ 'roughness': 0}] ] -for i in range(2): +for i, iorp in enumerate(ior_params): + color = iorp[0] + params = iorp[1] center = [[0, -35 - (5 * i), 0]] - for j in range(11): + for j in range(num_values): center[0][0] = -25 + 5 * j - sphere = actor.sphere(center, ior_params[i][0], radii=2, theta=32, - phi=32) - keys = list(ior_params[i][1]) - ior_params[i][1][keys[0]] = iors[j] - material.manifest_pbr(sphere, **ior_params[i][1]) + sphere = actor.sphere(center, color, radii=2, theta=32, phi=32) + keys = list(params) + params[keys[0]] = iors[j] + material.manifest_pbr(sphere, **params) scene.add(sphere) """ @@ -113,13 +117,12 @@ labels = ['Base IoR', 'Coat IoR'] -for i in range(2): +for i, l in enumerate(labels): pos = [-40, -35 - (5 * i), 0] - label = actor.vector_text(labels[i], pos=pos, scale=(.8, .8, .8), - color=(0, 0, 0)) + label = actor.vector_text(l, pos=pos, scale=(.8, .8, .8), color=(0, 0, 0)) scene.add(label) -for j in range(11): +for j in range(num_values): pos = [-26 + 5 * j, -32, 0] label = actor.vector_text('{:.02f}'.format(iors[j]), pos=pos, scale=(.8, .8, .8), color=(0, 0, 0)) From d032cd7feaeffd22a25a4c3f423c4a521a1011cc Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Thu, 27 Jan 2022 14:59:32 -0500 Subject: [PATCH 25/32] Added exception handling for cases when the number of filenames is not 6. --- fury/io.py | 2 ++ fury/tests/test_io.py | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/fury/io.py b/fury/io.py index 9bededbeb..0463c119d 100644 --- a/fury/io.py +++ b/fury/io.py @@ -31,6 +31,8 @@ def load_cubemap_texture(fnames, interpolate_on=True, mipmap_on=True): Cube map texture. """ + if len(fnames) != 6: + raise IOError("Expected 6 filenames, got {}".format(len(fnames))) texture = Texture() texture.CubeMapOn() for idx, fn in enumerate(fnames): diff --git a/fury/tests/test_io.py b/fury/tests/test_io.py index d35eb221b..80ccb0ed9 100644 --- a/fury/tests/test_io.py +++ b/fury/tests/test_io.py @@ -166,6 +166,9 @@ def test_load_cubemap_texture(): fname_path = pjoin(odir, f'test.{ext}') save_image(data, fname_path) + fnames = [fname_path] * 5 + npt.assert_raises(IOError, load_cubemap_texture, fnames) + fnames = [fname_path] * 6 texture = load_cubemap_texture(fnames) npt.assert_equal(texture.GetCubeMap(), True) @@ -175,6 +178,9 @@ def test_load_cubemap_texture(): npt.assert_equal(texture.GetInputDataObject(0, 0).GetDimensions(), (50, 50, 1)) + fnames = [fname_path] * 7 + npt.assert_raises(IOError, load_cubemap_texture, fnames) + def test_load_sprite_sheet(): sprite_URL = 'https://raw.githubusercontent.com/'\ From b60b3eb53c2f98bc192e63f0acb707d56b8cecce Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Thu, 27 Jan 2022 15:31:49 -0500 Subject: [PATCH 26/32] Simplified skybox parameters in Scene initialization function. --- docs/examples/viz_pbr_interactive.py | 2 +- fury/tests/test_window.py | 62 ++++++++++++++-------------- fury/window.py | 28 ++++++++----- 3 files changed, 50 insertions(+), 42 deletions(-) diff --git a/docs/examples/viz_pbr_interactive.py b/docs/examples/viz_pbr_interactive.py index 154e1b5d1..6c0ec0990 100644 --- a/docs/examples/viz_pbr_interactive.py +++ b/docs/examples/viz_pbr_interactive.py @@ -155,7 +155,7 @@ def win_callback(obj, event): the rendering of the skybox. """ -scene = window.Scene(skybox_tex=cubemap, render_skybox=True) +scene = window.Scene(skybox=cubemap) """ With the scene created, we can then populate it. In this demo we will only add diff --git a/fury/tests/test_window.py b/fury/tests/test_window.py index cd9141dbc..75daa567f 100644 --- a/fury/tests/test_window.py +++ b/fury/tests/test_window.py @@ -74,46 +74,18 @@ def test_scene(): 'Focal Point (0.00, 0.00, 0.00)\n ' 'View Up (0.00, 1.00, 0.00)') npt.assert_equal(err.getvalue().strip(), '') - # Test skybox_tex + # Test skybox scene = window.Scene() npt.assert_equal(scene.GetUseImageBasedLighting(), False) npt.assert_equal(scene.GetAutomaticLightCreation(), 1) npt.assert_equal(scene.GetSphericalHarmonics(), None) npt.assert_equal(scene.GetEnvironmentTexture(), None) test_tex = Texture() - scene = window.Scene(skybox_tex=test_tex) + scene = window.Scene(skybox=test_tex) npt.assert_equal(scene.GetUseImageBasedLighting(), True) npt.assert_equal(scene.GetAutomaticLightCreation(), 0) npt.assert_equal(scene.GetSphericalHarmonics(), None) npt.assert_equal(scene.GetEnvironmentTexture(), test_tex) - # Test render_skybox - test_tex = Texture() - test_tex.CubeMapOn() - checker_arr = np.array([[1, 0], [0, 1]], dtype=np.uint8) * 255 - for i in range(6): - vtk_img = ImageData() - vtk_img.SetDimensions(2, 2, 1) - img_arr = np.zeros((2, 2, 3), dtype=np.uint8) - img_arr[:, :, i // 2] = checker_arr - vtk_arr = numpy_support.numpy_to_vtk(img_arr.reshape((-1, 3), - order='F')) - vtk_arr.SetName('Image') - img_point_data = vtk_img.GetPointData() - img_point_data.AddArray(vtk_arr) - img_point_data.SetActiveScalars('Image') - test_tex.SetInputDataObject(i, vtk_img) - scene = window.Scene(skybox_tex=test_tex, render_skybox=True) - ss = window.snapshot(scene) - npt.assert_array_equal(ss[75, 75, :], [0, 0, 255]) - npt.assert_array_equal(ss[75, 225, :], [0, 0, 0]) - scene.yaw(90) - ss = window.snapshot(scene) - npt.assert_array_equal(ss[75, 75, :], [255, 0, 0]) - npt.assert_array_equal(ss[75, 225, :], [0, 0, 0]) - scene.pitch(90) - ss = window.snapshot(scene) - npt.assert_array_equal(ss[75, 75, :], [0, 0, 0]) - npt.assert_array_equal(ss[75, 225, :], [0, 255, 0]) def test_active_camera(): @@ -263,6 +235,36 @@ def test_order_transparent(): assert_greater(green_stronger, green_weaker) +def test_show_scene(): + test_tex = Texture() + test_tex.CubeMapOn() + checker_arr = np.array([[1, 0], [0, 1]], dtype=np.uint8) * 255 + for i in range(6): + vtk_img = ImageData() + vtk_img.SetDimensions(2, 2, 1) + img_arr = np.zeros((2, 2, 3), dtype=np.uint8) + img_arr[:, :, i // 2] = checker_arr + vtk_arr = numpy_support.numpy_to_vtk(img_arr.reshape((-1, 3), + order='F')) + vtk_arr.SetName('Image') + img_point_data = vtk_img.GetPointData() + img_point_data.AddArray(vtk_arr) + img_point_data.SetActiveScalars('Image') + test_tex.SetInputDataObject(i, vtk_img) + scene = window.Scene(skybox=test_tex) + ss = window.snapshot(scene) + npt.assert_array_equal(ss[75, 75, :], [0, 0, 255]) + npt.assert_array_equal(ss[75, 225, :], [0, 0, 0]) + scene.yaw(90) + ss = window.snapshot(scene) + npt.assert_array_equal(ss[75, 75, :], [255, 0, 0]) + npt.assert_array_equal(ss[75, 225, :], [0, 0, 0]) + scene.pitch(90) + ss = window.snapshot(scene) + npt.assert_array_equal(ss[75, 75, :], [0, 0, 0]) + npt.assert_array_equal(ss[75, 225, :], [0, 255, 0]) + + @pytest.mark.skipif(skip_win, reason="This test does not work on Windows." " Need to be introspected") def test_stereo(): diff --git a/fury/window.py b/fury/window.py index d0f333b6e..f41269729 100644 --- a/fury/window.py +++ b/fury/window.py @@ -33,26 +33,32 @@ class Scene(OpenGLRenderer): but also it provides access to all the functionality available in ``vtkRenderer`` if necessary. """ - def __init__(self, background=(0, 0, 0), skybox_tex=None, - render_skybox=False): - self.skybox = skybox_tex - self.render_skybox = render_skybox + def __init__(self, background=(0, 0, 0), skybox=None): + self.skybox = skybox self.skybox_actor = None - if skybox_tex: + if skybox: self.AutomaticLightCreationOff() self.UseImageBasedLightingOn() self.UseSphericalHarmonicsOff() - self.SetEnvironmentTexture(skybox_tex) - if render_skybox: - self.skybox_actor = Skybox() - self.skybox_actor.SetTexture(skybox_tex) - self.skybox_actor.GammaCorrectOn() - self.add(self.skybox_actor) + self.SetEnvironmentTexture(skybox) + self.show_skybox() def background(self, color): """Set a background color.""" self.SetBackground(color) + def show_skybox(self, gamma_correct=True): + if self.skybox: + self.skybox_actor = Skybox() + self.skybox_actor.SetTexture(self.skybox) + if gamma_correct: + self.skybox_actor.GammaCorrectOn() + self.add(self.skybox_actor) + + def hide_skybox(self): + if self.skybox: + self.rm(self.skybox_actor) + def add(self, *actors): """Add an actor to the scene.""" for actor in actors: From 58d8e5bfa92cf858b2b5fce5523260ddcfe91a72 Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Thu, 27 Jan 2022 16:55:41 -0500 Subject: [PATCH 27/32] Added show_skybox and hide_skybox functions to Scene class. --- fury/tests/test_window.py | 80 +++++++++++++++++++++++++++++++++++++++ fury/window.py | 21 ++++++++-- 2 files changed, 97 insertions(+), 4 deletions(-) diff --git a/fury/tests/test_window.py b/fury/tests/test_window.py index 75daa567f..c54cf2f51 100644 --- a/fury/tests/test_window.py +++ b/fury/tests/test_window.py @@ -86,6 +86,36 @@ def test_scene(): npt.assert_equal(scene.GetAutomaticLightCreation(), 0) npt.assert_equal(scene.GetSphericalHarmonics(), None) npt.assert_equal(scene.GetEnvironmentTexture(), test_tex) + # Test automatically shown skybox + test_tex = Texture() + test_tex.CubeMapOn() + checker_arr = np.array([[1, 0], [0, 1]], dtype=np.uint8) * 255 + for i in range(6): + vtk_img = ImageData() + vtk_img.SetDimensions(2, 2, 1) + img_arr = np.zeros((2, 2, 3), dtype=np.uint8) + img_arr[:, :, i // 2] = checker_arr + vtk_arr = numpy_support.numpy_to_vtk(img_arr.reshape((-1, 3), + order='F')) + vtk_arr.SetName('Image') + img_point_data = vtk_img.GetPointData() + img_point_data.AddArray(vtk_arr) + img_point_data.SetActiveScalars('Image') + test_tex.SetInputDataObject(i, vtk_img) + scene = window.Scene(skybox=test_tex) + report = window.analyze_scene(scene) + npt.assert_equal(report.actors, 1) + ss = window.snapshot(scene) + npt.assert_array_equal(ss[75, 75, :], [0, 0, 255]) + npt.assert_array_equal(ss[75, 225, :], [0, 0, 0]) + scene.yaw(90) + ss = window.snapshot(scene) + npt.assert_array_equal(ss[75, 75, :], [255, 0, 0]) + npt.assert_array_equal(ss[75, 225, :], [0, 0, 0]) + scene.pitch(90) + ss = window.snapshot(scene) + npt.assert_array_equal(ss[75, 75, :], [0, 0, 0]) + npt.assert_array_equal(ss[75, 225, :], [0, 255, 0]) def test_active_camera(): @@ -159,6 +189,39 @@ def test_active_camera(): npt. assert_equal(view_up, cam.GetViewUp()) +def test_hide_skybox(): + # Test scene created without skybox + scene = window.Scene() + npt.assert_warns(UserWarning, scene.show_skybox) + # Test removing automatically shown skybox + test_tex = Texture() + test_tex.CubeMapOn() + checker_arr = np.array([[1, 0], [0, 1]], dtype=np.uint8) * 255 + for i in range(6): + vtk_img = ImageData() + vtk_img.SetDimensions(2, 2, 1) + img_arr = np.zeros((2, 2, 3), dtype=np.uint8) + img_arr[:, :, i // 2] = checker_arr + vtk_arr = numpy_support.numpy_to_vtk(img_arr.reshape((-1, 3), + order='F')) + vtk_arr.SetName('Image') + img_point_data = vtk_img.GetPointData() + img_point_data.AddArray(vtk_arr) + img_point_data.SetActiveScalars('Image') + test_tex.SetInputDataObject(i, vtk_img) + scene = window.Scene(skybox=test_tex) + report = window.analyze_scene(scene) + npt.assert_equal(report.actors, 1) + ss = window.snapshot(scene) + npt.assert_array_equal(ss[75, 75, :], [0, 0, 255]) + npt.assert_array_equal(ss[75, 225, :], [0, 0, 0]) + scene.hide_skybox() + report = window.analyze_scene(scene) + npt.assert_equal(report.actors, 0) + ss = window.snapshot(scene) + npt.assert_array_equal(ss[75, 75, :], [0, 0, 0]) + + def test_parallel_projection(): scene = window.Scene() @@ -236,6 +299,10 @@ def test_order_transparent(): def test_show_scene(): + # Test scene created without skybox + scene = window.Scene() + npt.assert_warns(UserWarning, scene.show_skybox) + # Test automatically shown skybox test_tex = Texture() test_tex.CubeMapOn() checker_arr = np.array([[1, 0], [0, 1]], dtype=np.uint8) * 255 @@ -252,6 +319,8 @@ def test_show_scene(): img_point_data.SetActiveScalars('Image') test_tex.SetInputDataObject(i, vtk_img) scene = window.Scene(skybox=test_tex) + report = window.analyze_scene(scene) + npt.assert_equal(report.actors, 1) ss = window.snapshot(scene) npt.assert_array_equal(ss[75, 75, :], [0, 0, 255]) npt.assert_array_equal(ss[75, 225, :], [0, 0, 0]) @@ -263,6 +332,17 @@ def test_show_scene(): ss = window.snapshot(scene) npt.assert_array_equal(ss[75, 75, :], [0, 0, 0]) npt.assert_array_equal(ss[75, 225, :], [0, 255, 0]) + # Test skybox is not added twice + scene.show_skybox() + report = window.analyze_scene(scene) + npt.assert_equal(report.actors, 1) + # Test removing the skybox_actor and readding it + scene.rm(scene.skybox_actor) + report = window.analyze_scene(scene) + npt.assert_equal(report.actors, 0) + scene.add(scene.skybox_actor) + report = window.analyze_scene(scene) + npt.assert_equal(report.actors, 1) @pytest.mark.skipif(skip_win, reason="This test does not work on Windows." diff --git a/fury/window.py b/fury/window.py index f41269729..45e58b3f3 100644 --- a/fury/window.py +++ b/fury/window.py @@ -48,16 +48,29 @@ def background(self, color): self.SetBackground(color) def show_skybox(self, gamma_correct=True): + """ Add a skybox actor to the scene. + + Parameters + ---------- + gamma_correct : bool + If True, the skybox will be gamma corrected. + + """ if self.skybox: - self.skybox_actor = Skybox() - self.skybox_actor.SetTexture(self.skybox) - if gamma_correct: - self.skybox_actor.GammaCorrectOn() + if self.skybox_actor is None: + self.skybox_actor = Skybox() + self.skybox_actor.SetTexture(self.skybox) + if gamma_correct: + self.skybox_actor.GammaCorrectOn() self.add(self.skybox_actor) + else: + warn('Scene created without a skybox. Nothing to show.') def hide_skybox(self): if self.skybox: self.rm(self.skybox_actor) + else: + warn('Scene created without a skybox. Nothing to hide.') def add(self, *actors): """Add an actor to the scene.""" From 4ef23be9196845d7d90bdb5db691a306953617e0 Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Thu, 27 Jan 2022 17:32:57 -0500 Subject: [PATCH 28/32] Refactored actor param to avoid issues with module. --- fury/utils.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/fury/utils.py b/fury/utils.py index 70efbea43..e0aa037fc 100644 --- a/fury/utils.py +++ b/fury/utils.py @@ -1215,12 +1215,12 @@ def colors_from_actor(actor, array_name='colors', as_vtk=False): as_vtk=as_vtk) -def normals_from_actor(actor): +def normals_from_actor(act): """Access normals from actor which uses polydata. Parameters ---------- - actor : actor + act : actor Returns ------- @@ -1228,16 +1228,16 @@ def normals_from_actor(actor): Normals """ - polydata = actor.GetMapper().GetInput() + polydata = act.GetMapper().GetInput() return get_polydata_normals(polydata) -def tangents_from_actor(actor): +def tangents_from_actor(act): """Access tangents from actor which uses polydata. Parameters ---------- - actor : actor + act : actor Returns ------- @@ -1245,7 +1245,7 @@ def tangents_from_actor(actor): Tangents """ - polydata = actor.GetMapper().GetInput() + polydata = act.GetMapper().GetInput() return get_polydata_tangents(polydata) @@ -1274,12 +1274,12 @@ def array_from_actor(actor, array_name, as_vtk=False): return numpy_support.vtk_to_numpy(vtk_array) -def normals_to_actor(actor, normals): +def normals_to_actor(act, normals): """Set normals to actor which uses polydata. Parameters ---------- - actor : actor + act : actor normals : normals, represented as 2D ndarrays (Nx3) (one per vertex) Returns @@ -1287,23 +1287,23 @@ def normals_to_actor(actor, normals): actor """ - polydata = actor.GetMapper().GetInput() + polydata = act.GetMapper().GetInput() set_polydata_normals(polydata, normals) - return actor + return act -def tangents_to_actor(actor, tangents): +def tangents_to_actor(act, tangents): """Set tangents to actor which uses polydata. Parameters ---------- - actor : actor + act : actor tangents : tangents, represented as 2D ndarrays (Nx3) (one per vertex) """ - polydata = actor.GetMapper().GetInput() + polydata = act.GetMapper().GetInput() set_polydata_tangents(polydata, tangents) - return actor + return act def compute_bounds(actor): From ba3121263ca49b716004fa7a53e6e7a958f73b82 Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Thu, 27 Jan 2022 17:55:04 -0500 Subject: [PATCH 29/32] show_skybox and hide_skybox have been simplified to the skybox function. --- fury/tests/test_window.py | 56 +++++++++++---------------------------- fury/window.py | 41 ++++++++++++++-------------- 2 files changed, 36 insertions(+), 61 deletions(-) diff --git a/fury/tests/test_window.py b/fury/tests/test_window.py index c54cf2f51..0fa32d97b 100644 --- a/fury/tests/test_window.py +++ b/fury/tests/test_window.py @@ -189,39 +189,6 @@ def test_active_camera(): npt. assert_equal(view_up, cam.GetViewUp()) -def test_hide_skybox(): - # Test scene created without skybox - scene = window.Scene() - npt.assert_warns(UserWarning, scene.show_skybox) - # Test removing automatically shown skybox - test_tex = Texture() - test_tex.CubeMapOn() - checker_arr = np.array([[1, 0], [0, 1]], dtype=np.uint8) * 255 - for i in range(6): - vtk_img = ImageData() - vtk_img.SetDimensions(2, 2, 1) - img_arr = np.zeros((2, 2, 3), dtype=np.uint8) - img_arr[:, :, i // 2] = checker_arr - vtk_arr = numpy_support.numpy_to_vtk(img_arr.reshape((-1, 3), - order='F')) - vtk_arr.SetName('Image') - img_point_data = vtk_img.GetPointData() - img_point_data.AddArray(vtk_arr) - img_point_data.SetActiveScalars('Image') - test_tex.SetInputDataObject(i, vtk_img) - scene = window.Scene(skybox=test_tex) - report = window.analyze_scene(scene) - npt.assert_equal(report.actors, 1) - ss = window.snapshot(scene) - npt.assert_array_equal(ss[75, 75, :], [0, 0, 255]) - npt.assert_array_equal(ss[75, 225, :], [0, 0, 0]) - scene.hide_skybox() - report = window.analyze_scene(scene) - npt.assert_equal(report.actors, 0) - ss = window.snapshot(scene) - npt.assert_array_equal(ss[75, 75, :], [0, 0, 0]) - - def test_parallel_projection(): scene = window.Scene() @@ -298,11 +265,11 @@ def test_order_transparent(): assert_greater(green_stronger, green_weaker) -def test_show_scene(): +def test_skybox(): # Test scene created without skybox scene = window.Scene() - npt.assert_warns(UserWarning, scene.show_skybox) - # Test automatically shown skybox + npt.assert_warns(UserWarning, scene.skybox) + # Test removing automatically shown skybox test_tex = Texture() test_tex.CubeMapOn() checker_arr = np.array([[1, 0], [0, 1]], dtype=np.uint8) * 255 @@ -333,14 +300,23 @@ def test_show_scene(): npt.assert_array_equal(ss[75, 75, :], [0, 0, 0]) npt.assert_array_equal(ss[75, 225, :], [0, 255, 0]) # Test skybox is not added twice - scene.show_skybox() + scene.skybox() report = window.analyze_scene(scene) npt.assert_equal(report.actors, 1) - # Test removing the skybox_actor and readding it - scene.rm(scene.skybox_actor) + # Test make skybox invisible + scene.skybox(visible=False) report = window.analyze_scene(scene) npt.assert_equal(report.actors, 0) - scene.add(scene.skybox_actor) + ss = window.snapshot(scene) + npt.assert_array_equal(ss[75, 75, :], [0, 0, 0]) + scene.yaw(90) + ss = window.snapshot(scene) + npt.assert_array_equal(ss[75, 75, :], [0, 0, 0]) + scene.pitch(90) + ss = window.snapshot(scene) + npt.assert_array_equal(ss[75, 225, :], [0, 0, 0]) + # Test make skybox visible again + scene.skybox(visible=True) report = window.analyze_scene(scene) npt.assert_equal(report.actors, 1) diff --git a/fury/window.py b/fury/window.py index 45e58b3f3..7cf0717bb 100644 --- a/fury/window.py +++ b/fury/window.py @@ -34,43 +34,42 @@ class Scene(OpenGLRenderer): available in ``vtkRenderer`` if necessary. """ def __init__(self, background=(0, 0, 0), skybox=None): - self.skybox = skybox - self.skybox_actor = None + self.__skybox = skybox + self.__skybox_actor = None if skybox: self.AutomaticLightCreationOff() self.UseImageBasedLightingOn() self.UseSphericalHarmonicsOff() - self.SetEnvironmentTexture(skybox) - self.show_skybox() + self.SetEnvironmentTexture(self.__skybox) + self.skybox() def background(self, color): """Set a background color.""" self.SetBackground(color) - def show_skybox(self, gamma_correct=True): - """ Add a skybox actor to the scene. + def skybox(self, visible=True, gamma_correct=True): + """Show or hide the skybox. Parameters ---------- + visible : bool + Whether to show the skybox or not. gamma_correct : bool - If True, the skybox will be gamma corrected. + Whether to apply gamma correction to the skybox or not. """ - if self.skybox: - if self.skybox_actor is None: - self.skybox_actor = Skybox() - self.skybox_actor.SetTexture(self.skybox) - if gamma_correct: - self.skybox_actor.GammaCorrectOn() - self.add(self.skybox_actor) - else: - warn('Scene created without a skybox. Nothing to show.') - - def hide_skybox(self): - if self.skybox: - self.rm(self.skybox_actor) + if self.__skybox: + if visible: + if self.__skybox_actor is None: + self.__skybox_actor = Skybox() + self.__skybox_actor.SetTexture(self.__skybox) + if gamma_correct: + self.__skybox_actor.GammaCorrectOn() + self.add(self.__skybox_actor) + else: + self.rm(self.__skybox_actor) else: - warn('Scene created without a skybox. Nothing to hide.') + warn('Scene created without a skybox. Nothing to show or hide.') def add(self, *actors): """Add an actor to the scene.""" From 6bb04dea7ee7a356c1ce8e6305093a3c4be01c9b Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Thu, 27 Jan 2022 18:14:42 -0500 Subject: [PATCH 30/32] Removed build_labels function. --- docs/examples/viz_pbr_interactive.py | 48 ++++++++++------------------ 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/docs/examples/viz_pbr_interactive.py b/docs/examples/viz_pbr_interactive.py index 6c0ec0990..b24c55ba7 100644 --- a/docs/examples/viz_pbr_interactive.py +++ b/docs/examples/viz_pbr_interactive.py @@ -16,27 +16,6 @@ tangents_from_direction_of_anisotropy) -""" -This function will help us to define the appearance of our labels. -""" - - -def build_label(text, font_size=16, color=(1, 1, 1), bold=False, italic=False, - shadow=False): - label = ui.TextBlock2D() - label.message = text - label.font_size = font_size - label.font_family = 'Arial' - label.justification = 'left' - label.bold = bold - label.italic = italic - label.shadow = shadow - label.actor.GetTextProperty().SetBackgroundColor(0, 0, 0) - label.actor.GetTextProperty().SetBackgroundOpacity(0.0) - label.color = color - return label - - """ The following functions will help us to manage the sliders events. """ @@ -215,17 +194,22 @@ def win_callback(obj, event): we need for this demo. And then add them to the panel. """ -slider_label_metallic = build_label('Metallic') -slider_label_roughness = build_label('Roughness') -slider_label_anisotropy = build_label('Anisotropy') -slider_label_anisotropy_rotation = build_label('Anisotropy Rotation') -slider_label_anisotropy_direction_x = build_label('Anisotropy Direction X') -slider_label_anisotropy_direction_y = build_label('Anisotropy Direction Y') -slider_label_anisotropy_direction_z = build_label('Anisotropy Direction Z') -slider_label_coat_strength = build_label('Coat Strength') -slider_label_coat_roughness = build_label('Coat Roughness') -slider_label_base_ior = build_label('Base IoR') -slider_label_coat_ior = build_label('Coat IoR') +slider_label_metallic = ui.TextBlock2D(text='Metallic', font_size=16) +slider_label_roughness = ui.TextBlock2D(text='Roughness', font_size=16) +slider_label_anisotropy = ui.TextBlock2D(text='Anisotropy', font_size=16) +slider_label_anisotropy_rotation = ui.TextBlock2D( + text='Anisotropy Rotation', font_size=16) +slider_label_anisotropy_direction_x = ui.TextBlock2D( + text='Anisotropy Direction X', font_size=16) +slider_label_anisotropy_direction_y = ui.TextBlock2D( + text='Anisotropy Direction Y', font_size=16) +slider_label_anisotropy_direction_z = ui.TextBlock2D( + text='Anisotropy Direction Z', font_size=16) +slider_label_coat_strength = ui.TextBlock2D(text='Coat Strength', font_size=16) +slider_label_coat_roughness = ui.TextBlock2D( + text='Coat Roughness', font_size=16) +slider_label_base_ior = ui.TextBlock2D(text='Base IoR', font_size=16) +slider_label_coat_ior = ui.TextBlock2D(text='Coat IoR', font_size=16) control_panel.add_element(slider_label_metallic, (.01, .95)) control_panel.add_element(slider_label_roughness, (.01, .86)) From cb2057201ff4634c5450293617eaea8f8068eaf6 Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Fri, 28 Jan 2022 12:10:32 -0500 Subject: [PATCH 31/32] Improved description of skybox parameter in interactive demo. --- docs/examples/viz_pbr_interactive.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/examples/viz_pbr_interactive.py b/docs/examples/viz_pbr_interactive.py index b24c55ba7..e1e8400af 100644 --- a/docs/examples/viz_pbr_interactive.py +++ b/docs/examples/viz_pbr_interactive.py @@ -129,9 +129,8 @@ def win_callback(obj, event): """ The Scene object in FURY can handle cube map textures and extract light information from them, so it can be used to create more plausible materials -interactions. The ``skybox_tex`` parameter is the one performing the previously -described process. On the other hand, the ``render_skybox`` parameter toggles -the rendering of the skybox. +interactions. The ``skybox`` parameter takes as input a cube map texture and +performs the previously described process. """ scene = window.Scene(skybox=cubemap) From 1fdcb582e3a3c70e4a155513eba3fc1c2855383a Mon Sep 17 00:00:00 2001 From: Javier Guaje Date: Fri, 28 Jan 2022 14:08:07 -0500 Subject: [PATCH 32/32] Added helper class to manage PBR properties. --- docs/examples/viz_pbr_interactive.py | 57 +++++------ fury/material.py | 147 ++++++++++++++++++++++++--- 2 files changed, 156 insertions(+), 48 deletions(-) diff --git a/docs/examples/viz_pbr_interactive.py b/docs/examples/viz_pbr_interactive.py index e1e8400af..1e7c68a5e 100644 --- a/docs/examples/viz_pbr_interactive.py +++ b/docs/examples/viz_pbr_interactive.py @@ -22,21 +22,18 @@ def change_slice_metallic(slider): - global pbr_params, sphere - pbr_params['metallic'] = slider.value - sphere.GetProperty().SetMetallic(pbr_params['metallic']) + global pbr_params + pbr_params.metallic = slider.value def change_slice_roughness(slider): - global pbr_params, sphere - pbr_params['roughness'] = slider.value - sphere.GetProperty().SetRoughness(pbr_params['roughness']) + global pbr_params + pbr_params.roughness = slider.value def change_slice_anisotropy(slider): - global pbr_params, sphere - pbr_params['anisotropy'] = slider.value - sphere.GetProperty().SetAnisotropy(pbr_params['anisotropy']) + global pbr_params + pbr_params.anisotropy = slider.value def change_slice_anisotropy_direction_x(slider): @@ -61,34 +58,28 @@ def change_slice_anisotropy_direction_z(slider): def change_slice_anisotropy_rotation(slider): - global pbr_params, sphere - pbr_params['anisotropy_rotation'] = slider.value - sphere.GetProperty().SetAnisotropyRotation( - pbr_params['anisotropy_rotation']) + global pbr_params + pbr_params.anisotropy_rotation = slider.value def change_slice_coat_strength(slider): - global pbr_params, sphere - pbr_params['coat_strength'] = slider.value - sphere.GetProperty().SetCoatStrength(pbr_params['coat_strength']) + global pbr_params + pbr_params.coat_strength = slider.value def change_slice_coat_roughness(slider): - global pbr_params, sphere - pbr_params['coat_roughness'] = slider.value - sphere.GetProperty().SetCoatRoughness(pbr_params['coat_roughness']) + global pbr_params + pbr_params.coat_roughness = slider.value def change_slice_base_ior(slider): - global pbr_params, sphere - pbr_params['base_ior'] = slider.value - sphere.GetProperty().SetBaseIOR(pbr_params['base_ior']) + global pbr_params + pbr_params.base_ior = slider.value def change_slice_coat_ior(slider): - global pbr_params, sphere - pbr_params['coat_ior'] = slider.value - sphere.GetProperty().SetCoatIOR(pbr_params['coat_ior']) + global pbr_params + pbr_params.coat_ior = slider.value """ @@ -227,22 +218,22 @@ def win_callback(obj, event): """ slider_slice_metallic = ui.LineSlider2D( - initial_value=pbr_params['metallic'], max_value=1, length=195, + initial_value=pbr_params.metallic, max_value=1, length=195, text_template='{value:.1f}') slider_slice_roughness = ui.LineSlider2D( - initial_value=pbr_params['roughness'], max_value=1, length=195, + initial_value=pbr_params.roughness, max_value=1, length=195, text_template='{value:.1f}') slider_slice_anisotropy = ui.LineSlider2D( - initial_value=pbr_params['anisotropy'], max_value=1, length=195, + initial_value=pbr_params.anisotropy, max_value=1, length=195, text_template='{value:.1f}') slider_slice_anisotropy_rotation = ui.LineSlider2D( - initial_value=pbr_params['anisotropy_rotation'], max_value=1, length=195, + initial_value=pbr_params.anisotropy_rotation, max_value=1, length=195, text_template='{value:.1f}') slider_slice_coat_strength = ui.LineSlider2D( - initial_value=pbr_params['coat_strength'], max_value=1, length=195, + initial_value=pbr_params.coat_strength, max_value=1, length=195, text_template='{value:.1f}') slider_slice_coat_roughness = ui.LineSlider2D( - initial_value=pbr_params['coat_roughness'], max_value=1, length=195, + initial_value=pbr_params.coat_roughness, max_value=1, length=195, text_template='{value:.1f}') """ @@ -268,10 +259,10 @@ def win_callback(obj, event): """ slider_slice_base_ior = ui.LineSlider2D( - initial_value=pbr_params['base_ior'], min_value=1, max_value=2.3, + initial_value=pbr_params.base_ior, min_value=1, max_value=2.3, length=195, text_template='{value:.02f}') slider_slice_coat_ior = ui.LineSlider2D( - initial_value=pbr_params['coat_ior'], min_value=1, max_value=2.3, + initial_value=pbr_params.coat_ior, min_value=1, max_value=2.3, length=195, text_template='{value:.02f}') """ diff --git a/fury/material.py b/fury/material.py index 54744a79d..98b69afb4 100644 --- a/fury/material.py +++ b/fury/material.py @@ -5,6 +5,120 @@ from fury.lib import VTK_OBJECT, calldata_type +class __PBRParams: + """Helper class to manage PBR parameters. + + Attributes + ---------- + actor_properties : vtkProperty + The actor properties. + + Parameters + ---------- + metallic : float + Metallic or non-metallic (dielectric) shading computation value. Values + must be between 0.0 and 1.0. + roughness : float + Parameter used to specify how glossy the actor should be. Values must + be between 0.0 and 1.0. + anisotropy : float + Isotropic or anisotropic material parameter. Values must be between + 0.0 and 1.0. + anisotropy_rotation : float + Rotation of the anisotropy around the normal in a counter-clockwise + fashion. Values must be between 0.0 and 1.0. A value of 1.0 means a + rotation of 2 * pi. + coat_strength : float + Strength of the coat layer. Values must be between 0.0 and 1.0 (0.0 + means no clear coat will be modeled). + coat_roughness : float + Roughness of the coat layer. Values must be between 0.0 and 1.0. + base_ior : float + Index of refraction of the base material. Default is 1.5. Values must + be between 1.0 and 2.3. + coat_ior : float + Index of refraction of the coat material. Default is 1.5. Values must + be between 1.0 and 2.3. + """ + def __init__(self, actor_properties, metallic, roughness, + anisotropy, anisotropy_rotation, coat_strength, + coat_roughness, base_ior, coat_ior): + self.__actor_properties = actor_properties + self.__actor_properties.SetMetallic(metallic) + self.__actor_properties.SetRoughness(roughness) + self.__actor_properties.SetAnisotropy(anisotropy) + self.__actor_properties.SetAnisotropyRotation( + anisotropy_rotation) + self.__actor_properties.SetCoatStrength(coat_strength) + self.__actor_properties.SetCoatRoughness(coat_roughness) + self.__actor_properties.SetBaseIOR(base_ior) + self.__actor_properties.SetCoatIOR(coat_ior) + + @property + def metallic(self): + return self.__actor_properties.GetMetallic() + + @metallic.setter + def metallic(self, metallic): + self.__actor_properties.SetMetallic(metallic) + + @property + def roughness(self): + return self.__actor_properties.GetRoughness() + + @roughness.setter + def roughness(self, roughness): + self.__actor_properties.SetRoughness(roughness) + + @property + def anisotropy(self): + return self.__actor_properties.GetAnisotropy() + + @anisotropy.setter + def anisotropy(self, anisotropy): + self.__actor_properties.SetAnisotropy(anisotropy) + + @property + def anisotropy_rotation(self): + return self.__actor_properties.GetAnisotropyRotation() + + @anisotropy_rotation.setter + def anisotropy_rotation(self, anisotropy_rotation): + self.__actor_properties.SetAnisotropyRotation(anisotropy_rotation) + + @property + def coat_strength(self): + return self.__actor_properties.GetCoatStrength() + + @coat_strength.setter + def coat_strength(self, coat_strength): + self.__actor_properties.SetCoatStrength(coat_strength) + + @property + def coat_roughness(self): + return self.__actor_properties.GetCoatRoughness() + + @coat_roughness.setter + def coat_roughness(self, coat_roughness): + self.__actor_properties.SetCoatRoughness(coat_roughness) + + @property + def base_ior(self): + return self.__actor_properties.GetBaseIOR() + + @base_ior.setter + def base_ior(self, base_ior): + self.__actor_properties.SetBaseIOR(base_ior) + + @property + def coat_ior(self): + return self.__actor_properties.GetCoatIOR() + + @coat_ior.setter + def coat_ior(self, coat_ior): + self.__actor_properties.SetCoatIOR(coat_ior) + + def manifest_pbr(actor, metallic=0, roughness=.5, anisotropy=0, anisotropy_rotation=0, coat_strength=0, coat_roughness=0, base_ior=1.5, coat_ior=2): @@ -44,21 +158,24 @@ def manifest_pbr(actor, metallic=0, roughness=.5, anisotropy=0, try: prop.SetInterpolationToPBR() - pbr_params = {'metallic': metallic, 'roughness': roughness, - 'anisotropy': anisotropy, - 'anisotropy_rotation': anisotropy_rotation, - 'coat_strength': coat_strength, - 'coat_roughness': coat_roughness, - 'base_ior': base_ior, 'coat_ior': coat_ior} - - prop.SetMetallic(pbr_params['metallic']) - prop.SetRoughness(pbr_params['roughness']) - prop.SetAnisotropy(pbr_params['anisotropy']) - prop.SetAnisotropyRotation(pbr_params['anisotropy_rotation']) - prop.SetCoatStrength(pbr_params['coat_strength']) - prop.SetCoatRoughness(pbr_params['coat_roughness']) - prop.SetBaseIOR(pbr_params['base_ior']) - prop.SetCoatIOR(pbr_params['coat_ior']) + #pbr_params = {'metallic': metallic, 'roughness': roughness, + # 'anisotropy': anisotropy, + # 'anisotropy_rotation': anisotropy_rotation, + # 'coat_strength': coat_strength, + # 'coat_roughness': coat_roughness, + # 'base_ior': base_ior, 'coat_ior': coat_ior} + + pbr_params = __PBRParams(prop, metallic, roughness, anisotropy, + anisotropy_rotation, coat_strength, + coat_roughness, base_ior, coat_ior) + #prop.SetMetallic(pbr_params['metallic']) + #prop.SetRoughness(pbr_params['roughness']) + #prop.SetAnisotropy(pbr_params['anisotropy']) + #prop.SetAnisotropyRotation(pbr_params['anisotropy_rotation']) + #prop.SetCoatStrength(pbr_params['coat_strength']) + #prop.SetCoatRoughness(pbr_params['coat_roughness']) + #prop.SetBaseIOR(pbr_params['base_ior']) + #prop.SetCoatIOR(pbr_params['coat_ior']) return pbr_params except AttributeError: warnings.warn(