©Anton Podvalny

Table of Contents

Introduction


The VFB can be controlled programmatically via App SDK in many ways. One of them being by using various environment variables. Another is by getting and setting the VFB layer JSON string, which includes parsing it (easier in languages like Python and JavaScript, but difficult in C++ and C# without the help of specialized libraries). We introduce an API for easier manipulation of the VFB layers.

There are two abstractions created in every App SDK binding: the VFB Layer Manager abstraction, and the VFB Layer abstraction which is a handle to a real VFB Layer (since it is a handle, then there may be some cases when the current instance may be invalidated, and you have to get a new handle).

VFB Layer Manager


You can do the following operations using the Layer Manager:

  • reset the Layer Manager layers to default (there are specific condition one must meet before invoking this operation, which are described in the documentation);
  • load and save VFB layers as a file;
  • get and set VFB layers as a JSON string;
  • load OCIO or ICC configuration files and get/set some of the UI exposed properties related to them;
  • get the layer classes which are creatable by a user;
  • bake all layers to a LUT (.cube) file;
  • create or delete layers. There are some particularities to what layers you can create with the specified parent layer and what layers you can delete (not all are deletable);
  • access the common layers on a whim: Root layer, Display Correction layer, Stamp layer, Source layer, Denoiser layer, Sharpen/Blur layer, Lens Effects layer and Light Mix layer.

There are some auxiliary functions which can accumulate layers with some user specified property: e.g., get all the layers which are enabled or get all the layers with a specific class. If you want to perform thread-safe operations and batch operations, there are ways to lock and then unlock the Layer Manager

VFB Layer


If you choose to get a VFB Layer handle, there are multiple common operations you can perform:

  • check is it is a valid handle;
  • get the unique path from the root layer to the current layer using their indices as children of their parent layer (indices from the list of children of the parent layer). E.g. '/0/1' corresponds to get the Root layer, then get the child with index zero in the list of children of the Root layer. Then get the child of the zeroth child of the Root layer with index 1 in the list of the zeroth child of the Root layer;
  • get other more basic read-only properties like: the class of a layer, if the layer can be deleted or if it is blendable;
  • get/set properties like: the name of the layer, whether it is enabled, the opacity of a layer and the blend mode of the layer if it is blendable;
  • get the parent of a layer, its children, or get a specific child;
  • get the layer properties which can be modified (there are some which are not mutable and some which are immutable when specific conditions are present);
  • get the layer properties which are read-only, or you can get the layer properties of a specific type (like integer, float, enumerable, Color, etc.).

Some specific layer property methods are that you can:

  • get the layer display name;
  • reset the layer property to its default value;
  • check if it is read-only;
  • get its type;
  • get its flags;
  • get/set the value of a property depending on its type.

Specifics


Now it is good to discuss some of the peculiarities that we have mentioned earlier:

The first one is that if you want to work with the VFB layer settings from a loaded vrscene when you are not rendering at the moment, you must manage the "loading" and "unloading" of those settings (using fillSettingsVFBapplySettingsVFB and the Layer Manager method reset) on your own.

# Load scene from a file.
renderer.load(os.path.join(SCENE_PATH, 'intro.vrscene'))
# Fill the VFB layers settings from the SettingsVFB plugin to the VFB Layer Manager
renderer.vfb.applySettingsVFB()
# Now the VFB Layer Manager configuration is overwritten with the setting coming from SettingsVFB plugin
// Load scene from a file.
renderer.load("intro.vrscene");
// Fill the VFB layers settings from the SettingsVFB plugin to the VFB Layer Manager
renderer.vfb.applySettingsVFB();
// Now the VFB Layer Manager configuration is overwritten with the setting coming from SettingsVFB plugin
// Load scene from a file.
renderer.Load("intro.vrscene");
// Fill the VFB layers settings from the SettingsVFB plugin to the VFB Layer Manager
renderer.Vfb.ApplySettingsVFB();
// Now the VFB Layer Manager configuration is overwritten with the setting coming from SettingsVFB plugin
// Load scene from a file asynchronously.
renderer.load('intro.vrscene', function(err) {
    if (err) {
        // Scene was not loaded.
        throw err;
    }
    // Fill the VFB layers settings from the SettingsVFB plugin to the VFB Layer Manager
    renderer.vfb.applySettingsVFB();
    // Now the VFB Layer Manager configuration is overwritten with the setting coming from SettingsVFB plugin
});

When beginning a rendering, by default the parameters from the  SettingsVFB plugin vfb2_layers and cc_settings are used to overwrite the contents of the Layer Manager. If this is not a desired effect, then you can set the dont_affect_settings parameter of the SettingsVFB plugin in the scene to true (the default value is false). Then the contents of the Layer Manager will remain unchanged. If you don't want to use this parameter, you can use a combination of fillSettingsVFB and applySettingsVFB methods to write the changes of the layers to the SettingsVFB plugin, which will be later read when the rendering starts.

If the parameters vfb2_layers and cc_settings are not set on the SettingsVFB plugin, then the Layer Manager current configuration is used by default.

Example snippet using the dont_affect_settings parameter.

This method does not overwrite the vrscene parameters related to the VFB Layers settings.

# Get the SettingsVFB plugin instance if such a plugin exists in the vrscene
settingsVFBPlugins = renderer.classes.SettingsVFB.getInstances()
if len(settingsVFBPlugins) > 0:
    # Set the dont_affect_settings to true if you don't want the settings coming from
	# the vrscene file to override the VFB Layer Manager current layers configuration
    settingsVFBPlugins[0]['dont_affect_settings'] = True
# Start rendering.
renderer.startSync()
# Wait for rendering to end.
renderer.waitForRenderEnd()
// Get the SettingsVFB plugin instance if such a plugin exists in the vrscene
Plugin settingsVFB = renderer.getInstanceOf("SettingsVFB");
if (settingsVFB.isValid()) {
	// Set the dont_affect_settings to true if you don't want the settings coming from
	// the vrscene file to override the VFB Layer Manager current layers configuration
	settingsVFB.setValue("dont_affect_settings", true);
}
// Start rendering.
renderer.startSync();
// Wait for rendering to end.
renderer.waitForRenderEnd();
// Get the SettingsVFB plugin instance if such a plugin exists in the vrscene
SettingsVFB settingsVFB = (SettingsVFB)renderer.GetInstanceOf("SettingsVFB");
if (settingsVFB != null)
{
	// Set the dont_affect_settings to true if you don't want the settings coming from
	// the vrscene file to override the VFB Layer Manager current layers configuration
	settingsVFB.DontAffectSettings = true;
}
// Start rendering.
renderer.StartSync();
// Wait for rendering to end.
renderer.WaitForRenderEnd();
// Get the SettingsVFB plugin instance if such a plugin exists in the vrscene
settingsVFBPlugins = renderer.classes.SettingsVFB.getInstances();
if (settingsVFBPlugins.length > 0) {
    // Set the dont_affect_settings to true if you don't want the settings coming from
	// the vrscene file to override the VFB Layer Manager current layers configuration
    settingsVFBPlugins[0].dont_affect_settings = true;
}
// Start rendering.
renderer.start(function(err) {
    if (err) {
        // Couldn't start rendering.
        throw err;
    }

    // Wait for rendering to end.
    renderer.waitForRenderEnd(function() {

        // Closes the renderer.
        // The renderer object is unusable after closing since its resources
        // are freed. It should be released for garbage collection.
        renderer.close();
    });
});

And an example when we avoid using the dont_affect_settings parameter.

This method does overwrite the vrscene parameters related to the VFB Layers settings.

# Fill the VFB layers settings from the VFB Layer Manager to the SettingsVFB plugin
renderer.vfb.fillSettingsVFB()
# Start rendering.
renderer.startSync()
# Wait for rendering to end.
renderer.waitForRenderEnd()
// Fill the VFB layers settings from the VFB Layer Manager to the SettingsVFB plugin
renderer.vfb.fillSettingsVFB();
// Start rendering.
renderer.startSync();
// Wait for rendering to end.
renderer.waitForRenderEnd();
// Fill the VFB layers settings from the VFB Layer Manager to the SettingsVFB plugin
renderer.Vfb.FillSettingsVFB();
// Start rendering.
renderer.StartSync();
// Wait for rendering to end.
renderer.WaitForRenderEnd();
// Fill the VFB layers settings from the SettingsVFB plugin to the VFB Layer Manager
renderer.vfb.fillSettingsVFB();
// Now you can access and modify the VFB layers settings coming with this vrscene
// Start rendering.
renderer.start(function(err) {
    if (err) {
        // Couldn't start rendering.
        throw err;
    }

    // Wait for rendering to end.
    renderer.waitForRenderEnd(function() {

        // Closes the renderer.
        // The renderer object is unusable after closing since its resources
        // are freed. It should be released for garbage collection.
        renderer.close();
    });
});


As we said earlier, a VFB layer instance is a handle to the real layer, so it can be invalidated. Some of the events when this may happen are: after it is deleted successfully, after a load or a set operation, after a new render begins (if not specified with the dont_affect_settings parameter of the SettingsVFB plugin that the contents of the Layer Manager must not be overwritten with the ones coming from the SettingsVFB plugin, if any), etc.

Note that not all layer properties are available to be manipulated at all times. E.g., the OCIO related properties of the Display Corrections layer are available after the VFB is loaded, and the correct profile is selected.

Example


In this example we will show how to do some manipulations with this API. The full example is available as a file in the examples/{language}/advanced folder with infix vfb-layers-api.

# Load scene from a file.
renderer.load(os.path.join(SCENE_PATH, "intro.vrscene"))

# Fill the VFB layers settings from the SettingsVFB plugin to the VFB Layer Manager
renderer.vfb.applySettingsVFB()

lm = renderer.vfb.layerManager

# Lock the layers so it will be a thread-safe batch operation.
lm.lockLayers()

# Get a handle to the Display Correction (DC) layer.
dcLayer = lm.getDisplayCorrectionLayer()

# Create a cc exposure layer as a child of the DC layer.
# All of the creatable class name you can get as a list by calling the getCreatableLayerClasses method.
dcExpLayer = lm.createLayer("chaos.cc.exposure", dcLayer)

# Check validity of the handle. On an error, empty handles are returned.
if dcExpLayer.isValid():
	# Set some properties of the exposure layer.
	# All the methods may throw exceptions if an error occurs.
	dcExpLayer.setBlendMode("BlendMode_Exclusion")
	# All of the properties of a layer you can get as a list by calling getLayerPropertyNames.
	dcExpLayer.hilight_burn = 0.5

	# Create a MultiMatte mask layer whose parent is the exposure layer.
	dcExpMMMaskLayer = lm.createLayer("chaos.ref.re.colormask", dcExpLayer)
	if dcExpMMMaskLayer.isValid():
		# If you are not sure whether a property exists, you can search it by getting a list of all
		# modifiable layer fields.
		propsNames = dcExpMMMaskLayer.getLayerPropertiesOfType("Bool")
		for propName in propsNames:
			if propName == "use_intensity":
				dcExpMMMaskLayer.use_intensity = True

# Never forget to unlock the layers. Otherwise, the whole VFB freezes.
lm.unlockLayers()

# Fill the VFB layers settings from the VFB Layer Manager to the SettingsVFB plugin.
renderer.vfb.fillSettingsVFB()

# Start rendering.
renderer.startSync()

# Wait some time.
time.sleep(3)

# Lock the layers, so the user cannot access them from the UI.
lm.lockLayers()

# Get a new handle to the DC layer. The old one is invalid after the beginning of the rendering.
dcLayer = lm.getDisplayCorrectionLayer()

# Change the current DC profile to Gamma 2.2.
typeProfile = dcLayer.getLayerPropertyType("profile")
if  typeProfile == "IntEnum":
	# You can know all the properties of an enumerable by calling getLayerPropertyIntEnumValues.
	dcLayer.profile = "Gamma 2.2"

# Delete all mask layers.
masks = lm.findAllLayersWithLayerClass("chaos.ref.re.colormask")
for mask in masks:
	if mask.canDelete():
		lm.deleteLayer(mask)

# Set some Stamp layer parameters.
stampLayer = lm.getStampLayer()
stampLayer.stamp_font = {
	"fontFamily": "stampFontFamily_modern",
	"fontStyle": "stampFontStyle_italic",
}
stampLayer.stamp_string = "Incredible CCs"
stampLayer.stamp_color = vray.Color(1.0, 0.6, 0.6)
stampLayer.setEnabled(True)

# Never forget to unlock the layers. Otherwise the whole VFB freezes.
lm.unlockLayers()

# Wait for rendering to end.
renderer.waitForRenderEnd()

# If you want the changes you made while rendering to the layers to be saved to the vrscene.
renderer.vfb.fillSettingsVFB()
renderer.export("intro2.vrscene")
// Load scene from a file.
renderer.load("intro.vrscene");

// Fill the VFB layers settings from the SettingsVFB plugin to the VFB Layer Manager.
renderer.vfb.applySettingsVFB();

VRayRenderer::VFB::LayerManager& lm = renderer.vfb.layerManager;

// Lock the layers so it will be a thread-safe batch operation.
lm.lockLayers();

// Get a handle to the Display Correction (DC) layer.
VRayRenderer::VFB::Layer dcLayer = lm.getDisplayCorrectionLayer();

// Create a cc exposure layer as a child of the DC layer.
// All of the creatable class name you can get as a list by calling the getCreatableLayerClasses method.
VRayRenderer::VFB::Layer dcExpLayer = lm.createLayer("chaos.cc.exposure", dcLayer);
// Check validity of the handle. On an error, empty handles are returned.
if (dcExpLayer.isValid()) {
	// Set some properties of the exposure layer.
	// You can check if the operation has been successful by getting the return value.
	dcExpLayer.setBlendMode(VFBLayerProperty::BlendMode::BlendMode_Exclusion);
	// All of the properties of a layer you can get as a list by calling getLayerPropertyNames.
	dcExpLayer.setLayerPropertyFloatValue("hilight_burn", 0.5f);

	// Create a MultiMatte mask layer whose parent is the exposure layer.
	VRayRenderer::VFB::Layer dcExpMMMaskLayer = lm.createLayer("chaos.ref.re.colormask", dcExpLayer);
	if (dcExpMMMaskLayer.isValid()) {
		// If you are not sure whether a property exists, you can search it by getting a list of all
		// modifiable layer fields.
		StringList propsNames;
		if (dcExpMMMaskLayer.getLayerPropertiesOfType(VFBLayerProperty::Type::Bool, propsNames) == 0) {
			for (size_t i = 0; i < propsNames.size(); i++) {
				if (propsNames[i] == "use_intensity") {
					dcExpMMMaskLayer.setLayerPropertyBoolValue("use_intensity", true);
				}
			}
		}
	}
}
// Never forget to unlock the layers. Otherwise, the whole VFB freezes.
lm.unlockLayers();

// Fill the VFB layers settings from the VFB Layer Manager to the SettingsVFB plugin.
renderer.vfb.fillSettingsVFB();
// Start rendering.
renderer.startSync();

// Wait some time.
Sleep(3000);
// Lock the layers, so the user cannot access them from the UI.
lm.lockLayers();

// Get a new handle to the DC layer. The old one is invalid after the beginning of the rendering.
dcLayer = lm.getDisplayCorrectionLayer();

// Change the current DC profile to Gamma 2.2.
VFBLayerProperty::Type type;
dcLayer.getLayerPropertyType("profile", type);
if (type == VFBLayerProperty::Type::IntEnum) {
	// # You can know all the properties of an enumerable by calling getLayerPropertyIntEnumValues.
	dcLayer.setLayerPropertyIntByEnumLabel("profile", "Gamma 2.2");
}

// Delete all mask layers.
vector<VRayRenderer::VFB::Layer> masks = lm.findAllLayersWithLayerClass("chaos.ref.re.colormask");
for (size_t i = 0; i < masks.size(); i++) {
	bool isDeletable;
	masks[i].canDelete(isDeletable);
	if (isDeletable) {
		lm.deleteLayer(masks[i]);
	}
}

// Set some Stamp layer parameters.
VRayRenderer::VFB::Layer stampLayer = lm.getStampLayer();
VFBLayerProperty::StampFontDesc desc;
stampLayer.getLayerPropertyStampFontDescValue("stamp_font", desc);
desc.fontFamily = VFBLayerProperty::StampFontFamily::stampFontFamily_modern;
desc.fontStyle = VFBLayerProperty::StampFontStyle::stampFontStyle_italic;
stampLayer.setLayerPropertyStampFontDescValue("stamp_font", desc);
stampLayer.setLayerPropertyStampRawStringValue("stamp_string", "Incredible CCs");
stampLayer.setLayerPropertyColorValue("stamp_color", Color(1.0, 0.6, 0.6));
stampLayer.setEnabled(true);

// Never forget to unlock the layers. Otherwise the whole VFB freezes.
lm.unlockLayers();

// Wait for rendering to end.
renderer.waitForRenderEnd();

// If you want the changes you made while rendering to the layers to be saved to the vrscene.
renderer.vfb.fillSettingsVFB();
renderer.exportScene("intro2.vrscene");
// Load scene from a file.
renderer.Load("intro.vrscene");

// Fill the VFB layers settings from the SettingsVFB plugin to the VFB Layer Manager.
renderer.Vfb.ApplySettingsVFB();

VFB.VFBLayerManager lm = renderer.Vfb.LayerManager;

// Lock the layers so it will be a thread-safe batch operation.
lm.LockLayers();

// Get a handle to the Display Correction (DC) layer.
VFB.Layer dcLayer = lm.GetDisplayCorrectionLayer();

// Create a cc exposure layer as a child of the DC layer.
// All of the creatable class name you can get as a list by calling the GetCreatableLayerClasses method.
VFB.Layer dcExpLayer = lm.CreateLayer("chaos.cc.exposure", dcLayer);
// Check validity of the handle. On an error, empty handles are returned.
if (dcExpLayer.IsValid())
{
    // Set some properties of the exposure layer.
    // All the methods may throw exceptions if an error occurs.
    dcExpLayer.SetBlendMode(VRay.VFBLayerProperty.BlendMode.Exclusion);
    // All of the properties of a layer you can get as a list by calling GetLayerPropertyNames.
    dcExpLayer.SetLayerPropertyFloatValue("hilight_burn", 0.5f);

    // Create a MultiMatte mask layer whose parent is the exposure layer.
    VFB.Layer dcExpMMMaskLayer = lm.CreateLayer("chaos.ref.re.colormask", dcExpLayer);
    if (dcExpMMMaskLayer.IsValid())
    {
        // If you are not sure whether a property exists, you can search it by getting a list of all
        // modifiable layer fields.
        IEnumerable<string> propsNames = dcExpMMMaskLayer.GetLayerPropertiesOfType(VRay.VFBLayerProperty.Type.Bool);
        foreach (var propName in propsNames)
        {
            if (propName == "use_intensity")
            {
                dcExpMMMaskLayer.SetLayerPropertyBoolValue("use_intensity", true);
            }
        }
    }
}

// Never forget to unlock the layers. Otherwise, the whole VFB freezes.
lm.UnlockLayers();

// Fill the VFB layers settings from the VFB Layer Manager to the SettingsVFB plugin.
renderer.Vfb.FillSettingsVFB();
// Start rendering.
renderer.StartSync();

// Wait some time.
System.Threading.Thread.Sleep(3000);
// Lock the layers, so the user cannot access them from the UI.
lm.LockLayers();

// Get a new handle to the DC layer. The old one is invalid after the beginning of the rendering.
dcLayer = lm.GetDisplayCorrectionLayer();

// Change the current DC profile to Gamma 2.2.
VRay.VFBLayerProperty.Type type = dcLayer.GetLayerPropertyType("profile");
if (type == VRay.VFBLayerProperty.Type.IntEnum)
{
    // You can know all the properties of an enumerable by calling GetLayerPropertyIntEnumValues.
    dcLayer.SetLayerPropertyIntByEnumLabel("profile", "Gamma 2.2");
}

// Delete all mask layers.
IEnumerable<VFB.Layer> masks = lm.FindAllLayersWithLayerClass("chaos.ref.re.colormask");
foreach (var mask in masks)
{
    if (mask.CanDelete())
    {
        lm.DeleteLayer(mask);
    }
}

// Set some Stamp layer parameters.
VFB.Layer stampLayer = lm.GetStampLayer();
VRay.VFBLayerProperty.StampFontDesc desc = stampLayer.GetLayerPropertyStampFontDescValue("stamp_font");
desc.fontFamily = VRay.VFBLayerProperty.StampFontFamily.Modern;
desc.fontStyle = VRay.VFBLayerProperty.StampFontStyle.Italic;
stampLayer.SetLayerPropertyStampFontDescValue("stamp_font", desc);
stampLayer.SetLayerPropertyStampRawStringValue("stamp_string", "Incredible CCs");
stampLayer.SetLayerPropertyColorValue("stamp_color", new Color(1.0, 0.6, 0.6));
stampLayer.SetEnabled(true);

// Never forget to unlock the layers. Otherwise the whole VFB freezes.
lm.UnlockLayers();

// Wait for rendering to end.
renderer.WaitForRenderEnd();

// If you want the changes you made while rendering to the layers to be saved to the vrscene.
renderer.Vfb.FillSettingsVFB();
renderer.Export("intro2.vrscene");
// Load scene from a file asynchronously.
renderer.load('intro.vrscene', function (err) {
	if (err) {
		// Scene was not loaded.
		throw err;
	}

	// Fill the VFB layers settings from the SettingsVFB plugin to the VFB Layer Manager
	renderer.vfb.applySettingsVFB()

	var lm = renderer.vfb.layerManager;

	// Lock the layers so it will be a thread-safe batch operation.
	lm.lockLayers();

	// Get a handle to the Display Correction (DC) layer.
	var dcLayer = lm.getDisplayCorrectionLayer();

	// Create a cc exposure layer as a child of the DC layer.
	// All of the creatable class name you can get as a list by calling the getCreatableLayerClasses method.
	var dcExpLayer = lm.createLayer("chaos.cc.exposure", dcLayer);

	// Check validity of the handle. On an error, empty handles are returned.
	if (dcExpLayer.isValid()) {
		// Set some properties of the exposure layer.
		// All the methods may throw exceptions if an error occurs.
		dcExpLayer.setBlendMode("BlendMode_Exclusion");
		// All of the properties of a layer you can get as a list by calling getLayerPropertyNames.
		dcExpLayer.hilight_burn = 0.5;

		// Create a MultiMatte mask layer whose parent is the exposure layer.
		var dcExpMMMaskLayer = lm.createLayer("chaos.ref.re.colormask", dcExpLayer);
		if (dcExpMMMaskLayer.isValid()) {
			// If you are not sure whether a property exists, you can search it by getting a list of all
			// modifiable layer fields.
			var propsNames = dcExpMMMaskLayer.getLayerPropertiesOfType("Bool");
			for (var propName of propsNames) {
				if (propName === "use_intensity") {
					dcExpMMMaskLayer.use_intensity = true;
				}
			}

		}
	}

	// Never forget to unlock the layers. Otherwise, the whole VFB freezes.
	lm.unlockLayers();

	// Fill the VFB layers settings from the VFB Layer Manager to the SettingsVFB plugin.
	renderer.vfb.fillSettingsVFB();

	// Start rendering.
	renderer.start(function (err) {
		if (err) {
			// Couldn't start rendering.
			throw err;
		}

		// Wait some time.
		setTimeout(function () {
			// Lock the layers, so the user cannot access them from the UI.
			lm.lockLayers();

			// Get a new handle to the DC layer. The old one is invalid after the beginning of the rendering.
			dcLayer = lm.getDisplayCorrectionLayer();

			// Change the current DC profile to Gamma 2.2.
			var type = dcLayer.getLayerPropertyType("profile");
			if (type === "IntEnum") {
				// You can know all the properties of an enumerable by calling getLayerPropertyIntEnumValues.
				dcLayer.profile = "Gamma 2.2";
			}

			// Delete all mask layers.
			var masks = lm.findAllLayersWithLayerClass("chaos.ref.re.colormask");
			for (var mask of masks) {
				if (mask.canDelete()) {
					lm.deleteLayer(mask);
				}
			}

			// Set some Stamp layer parameters.
			var stampLayer = lm.getStampLayer();
			stampLayer.stamp_font = { "fontFamily": "stampFontFamily_modern", "fontStyle": "stampFontStyle_italic" };
			stampLayer.stamp_string = "Incredible CCs";
			stampLayer.stamp_color = vray.Color(1.0, 0.6, 0.6);
			stampLayer.setEnabled(true);

			// Never forget to unlock the layers. Otherwise the whole VFB freezes.
			lm.unlockLayers();

			// Wait for rendering to end.
			renderer.waitForRenderEnd(function () {
				// If you want the changes you made while rendering to the layers to be saved to the vrscene.
				renderer.vfb.fillSettingsVFB();
				renderer.export("intro2.vrscene", function () {
					// Some other work if any and then renderer.close().
				});
			});
		}, 3000);
	});
});
  • No labels
Was this helpful?