Op API

Op API提供了功能强大的C ++插件接口,用于处理场景图和修改属性。所有的Katana的发货Ops使用Op API编写。该API允许您创建可以任意创建和操作场景数据的插件。可以为Op提供任意数量的场景图输入,可以从这些输入的任何位置检查属性数据,并且可以在当前位置创建,删除和修改属性。操作人员还可以创建和删除子位置,甚至删除自己。

换句话说,您可以做的任何事情Katana节点,您可以使用Op。您可以使用Ops进行操作的示例包括:

使用上下文感知的生成器和导入器,

高级自定义合并操作,

实例化层次结构,

从碎片部分构建网络材料,以及

处理为人群生成几何体。

注意:  尽管Op API旨在代替Scene Graph Generator和Attribute Modifier插件API,但它们仍可以在2.0v1后的版本中使用Katana

本节涵盖了Op API的以下元素:

The cook interface,它是什么以及它如何适合Geolib3。

Op arguments并修改传递给子级的参数。

Scene graph creation和层次结构拓扑管理,包括如何创建和删除场景图位置,以及控制执行Op的位置。

Reading scene graph input来自潜在的许多输入以及相关问题。

CEL和其他实用程序功能(作为Op编写器)可用来完成常见任务。

Integrating your Op与节点图。

您可以在$KATANA_HOME/plugins/Src/Ops该目录保存了许多核心操作的源代码。以下是其中一些操作的简要概述,以及当前使用它们的示例:

AttributeCopy-提供AttributeCopy节点的实现,该节点将场景中一个位置的属性复制到另一个场景。

AttributeSet-AttributeSet节点的后端,它允许您在传入的场景图中的任意位置设置,更改和删除属性。

HierarchyCopy-与AttributeSet Op一样,它是HierarchyCopy节点的后端,允许您将场景图层次结构的任意部分复制到场景图的其他部分。

修剪-从场景中删除与您提供的CEL表达式匹配的所有位置。

StaticSceneCreate-根据您提供的一组参数生成静态层次结构。此Op是HierarchyCreate的核心,并被其他Op和节点广泛使用,以生成其所需的位置和属性的层次结构。例如,CameraCreate节点使用StaticSceneCreate Op生成相机位置所需的层次。

库克界面

Cook接口是Geolib3提供的用于实现Op功能的接口。当您的Op进入时,系统会向您传递此接口的有效实例cook()方法被调用。如上所述,此接口提供了允许您询问参数,创建或修改场景图拓扑以及读取场景图输入的方法。您可以在以下网站的Cook界面上找到可用方法的完整列表$KATANA_HOME/plugin_apis/include/FnGeolib/op/FnGeolibCookInterface.h.

运算论据

如前所述,Ops提供了两种输入形式:由上游Ops创建的场景图输入和Op自变量,它们传递给Op以配置其运行方式。用户自变量的示例包括描述Op应该运行的位置的CEL语句,指向应该加载的几何缓存的文件路径或Op应该创建的子位置的列表。

我们将首先研究询问参数的简单情况,然后研究将参数递归传递到子位置的常见模式。

阅读论点

参数作为FnAttribute类。cook接口具有以下函数调用以检索Op参数:

FnAttribute::Attribute getOpArg(     const std::string& specificArgName = std::string()) const;

例如,StaticSceneCreate Op接受一个名为a包含属性列表,其中包含要在给定位置设置的值。显示为:

StaticSceneCreate处理a参数如下:

FnAttribute::GroupAttribute a = interface.getOpArg("a"); if (a.isValid()) {     for (int i = 0; i < a.getNumberOfChildren(); ++i)     {         interface.setAttr(a.getChildName(i), a.getChildByIndex(i));     } }

注意:  使用属性检索属性后,检查属性的有效性很重要isValid() 呼叫。每次从Cook界面返回属性时,都应检查属性的有效性。

将参数传递给子位置

有一种通用的递归方法可以将参数向下传递到运行Op的子位置。StaticSceneCreate Op很好地说明了这种模式。

StaticSceneCreate设置属性并根据传递给它的参数之一的值创建子位置的层次结构。此参数是一个GroupAttribute,它为每个位置描述:

a -属性值

c -任何子位置的名称

x -是否需要在该位置评估其他Op

为了将参数传递给它创建的子代,它会剥离子协议的较低层c论证并将其传递给其子级。从概念上讲,您可以考虑如下( ax为了简洁起见,将其省略):

在其当前位置运行的Op读取acx。对于每个子GroupAttribute c它使用GroupAttribute的名称(例如child1 / child2)创建一个新的子位置,并将GroupAttribute作为该位置的参数传递。

在代码中创建子代使用以下关键函数调用:

void createChild(const std::string& name,                  const std::string& optype = "",                  const FnAttribute::Attribute& args = FnAttribute::Attribute(),                  ResetRoot resetRoot = ResetRootAuto,                  void* privateData = 0x0,                  void (*deletePrivateData)(void* data) = 0x0);

createChild()函数将在其中评估Op的位置创建一个子级。该函数还指示运行时应在此处运行的Op的类型(默认情况下,与调用的Op的类型相同) createChild())以及应传递给它的参数。在StaticSceneCreate中,其外观如下:

for (int childindex = 0; childindex < c.getNumberOfChildren(); ++childindex) {     std::string childName = c.getChildName(childindex);     FnAttribute::GroupAttribute childArgs = c.getChildByIndex(childindex);     interface.createChild(childName, "", childArgs); }

场景图创建

操作的主要任务之一是生成场景图的位置和属性。为此,Op API提供了一组丰富的功能。有五个关键功能可用于修改场景图拓扑并控制Op的执行,我们将在下面进行解释。

注意:  重要的是要记住这里描述的功能集和本文描述的功能集之间的区别。 读取场景图输入。此处描述的所有功能都可在output of an Op at a given Scene Graph location。中描述的功能读取场景图输入仅与读取场景图数据有关input给定场景图位置的Op的大小不变。

setAttr()函数

void setAttr(const std::string& attrName,              const FnAttribute::Attribute& value,              const bool groupInherit = true);

setAttr()函数允许您在当前正在评估您的Op的位置设置属性值。例如,设置一个StringAttribute您可以在Op的根位置执行以下操作:

if (interface.atRoot()) {     interface.setAttr("myAttr", FnAttribute::StringAttribute("Val")); }

除了当前正在评估您的Op的位置以外,无法在其他位置设置属性。如果你打电话setAttr()对于同一位置上多次给定的属性名称,最后一个调用的名称是使用的名称。的groupInherit参数用于确定属性是否应由其子级继承。

注意:  以来setAttr()在Op的输出上设置值,而getAttr()正在读取给定输入上的不变值,如果调用setAttr()紧接着是getAttr(),结果仍然只是相关输入的值,而不是返回由setAttr()

createChild()函数

void createChild(const std::string& name,                  const std::string& optype = "",                  const FnAttribute::Attribute& args = FnAttribute::Attribute(),                  ResetRoot resetRoot = ResetRootAuto,                  void* privateData = 0x0,                  void (*deletePrivateData)(void* data) = 0x0);

createChild()函数可让您在要评估Op的场景图位置下创建子代。在最简单的情况下,它需要创建位置的名称,以及应传递给在该位置求值的Op的参数。例如:

interface.createChild(childName, "", childArgs);

如果将optype指定为空字符串,则在子位置评估名为create child的相同Op。但是,您可以指定任何其他optype并改为运行。

注意:  多次致电createChild()对于相同的命名子位置,将导致使用最后指定的optype,也就是说,连续调用createChild()掩盖以前的电话。

resetRoot参数采用以下三个值之一:

ResetRootTrue -在新位置评估的Op的根位置将重置为新位置路径。

ResetRootAuto (默认设置)-仅当optype与Op调用不同时才重置根位置createChild()

ResetRootFalse -在新位置评估的Op的根位置继承自称为createChild()

此参数控制用作rootLocation在子位置运行Op时。

execOp()函数

void execOp(const std::string& opType,             const FnAttribute::GroupAttribute& args);

当Geolib3 Runtime评估OpTree时,它是静态的并且是固定的。cook界面提供了许多功能,使您可以请求在OpTree的评估时间内执行在构建OpTree时未声明的Ops。

我们已经看到了createChild()通过允许您指定在子位置运行哪个Op来执行此操作。的execOp()函数允许Op直接调用另一个Op的执行,从而提供另一种机制来评估Op,而原始OpTree中没有直接声明该Op。这不同于createChild()行为,我们以多种方式声明要在子位置运行的另一个Op,包括:

应该将其视为另一个Op的一次执行,并且

在中指定的Op execOp()评估呼叫是否就像在与调用方相同的根位置在同一位置运行。

你可以看到execOp()在StaticSceneCreate Op中进行操作,其中Op类型在x参数:

// Exec some ops? FnAttribute::GroupAttribute opGroups = interface.getOpArg("x"); if (opGroups.isValid()) {     for (int childindex = 0; childindex < opGroups.getNumberOfChildren();          ++childindex)     {          ...          if (!opType.isValid() || !opArgs.isValid())          {              continue;          }          interface.execOp(opType.getValue("", false), opArgs);      } }

deleteSelf()函数

void deleteSelf();

到目前为止,我们只看到了向场景图添加数据的机制,但是deleteSelf()功能及相关功能deleteChild()允许您从场景图中删除位置。它们的行为是不言自明的,但是其副作用却不那么直观,因此在读取场景图输入。不过,目前为止,通过使用deleteSelf()函数调用如下所示:

// Use CEL Utility function to evaluate CEL expression FnAttribute::StringAttribute celAttr = interface.getOpArg("CEL"); if (!celAttr.isValid())     return;   Foundry::Katana::MatchesCELInfo info; Foundry::Katana::MatchesCEL(info, interface, celAttr);   if (!info.matches)     return; // Otherwise, delete myself interface.deleteSelf();   return;

stopChildTraversal()函数

void stopChildTraversal();

stopChildTraversal()函数是允许您控制Op运行在哪个位置的功能之一。它会在正在评估的当前位置的任何子级处停止执行此Op。最好以示例方式进行说明。

假设我们有一个输入场景:

/root

/world

/light

说我们想要的是:

/root

/world

/geo

/taco

/light

因此,我们使用StaticSceneCreate Op在起始位置/ root / world处创建此附加层次结构:

/geo

/taco

但是,如果我们不打电话stopChildTraversal()当StaticSceneCreate Op在/root/world然后这两个操作都运行/root/world/root/world/light, 导致:

/root

/world

/geo

/taco

/light

/geo

/taco

总而言之, stopChildTraversal()阻止您的Op在其输入中存在的任何子位置自动进行评估。最常见的用途stopChildTraversal()是为了提高效率。例如,如果我们可以通过查看一个CEL表达式来确定此Op在层次结构中比当前位置更深的任何位置都没有作用,则最好调用stopChildTraversal()这样我们甚至都不会在任何子位置调用此Op。

读取场景图输入

有多种功能可以读取上游Ops产生的输入场景图。所有这些功能仅允许读取功能;输入场景是不可变的。

getNumInputs()函数

int getNumInputs() const;

一个Op可以将多个其他Op的输出作为其输入。显而易见的用例是,您希望将由不同OpTree生成的多个场景图合并到一个场景图中,比较两个场景图状态中的属性值,或将一个场景图复制到另一个场景中。的getNumInputs()函数可让您确定要输入的操作数,这是询问场景图数据的OpTree的不同分支的前提。

警告:  值得注意的是,考虑到Geolib3的延迟处理模型,“获取”功能(例如getAttr()getPotentialChildren()doesInputExist() ,可能会要求尚未计算的场景图信息。

在这种情况下,您的Op的执行将中止(使用异常),并在请求的位置准备就绪时重新安排执行。因此,Op编写者不应尝试用“(...)”盲目捕获所有异常,而且,应尝试编写异常安全代码。

如果用户Op确实意外捕获了这些异常之一,则运行时将检测到该异常并认为结果无效,从而在场景图中生成错误。

如果您的Op仅从其默认输入位置(和索引)或其父级进行读取,则“重做”不太可能发生。但是,对于分散的访问查询,无论是在输入位置路径上还是在输入索引上,都可能会“重做”。如果Op需要从多个位置进行分散的访问查询,否则这些查询会具有不幸的性能特征,因此可以通过API调用- prefetch() -可用,稍后将进行详细讨论。

getAttr()函数

FnAttribute::Attribute getAttr(     const std::string& attrName,     const std::string& inputLocationPath = std::string(),     int inputIndex = kFnKatGeolibDefaultInput) const;

通常有必要根据存储在另一个属性中的结果执行某些操作或计算一个值。的getAttr()函数允许您通过提供属性名称和场景图位置路径(绝对或相对)来询问传入场景图的任何部分。此外,您可以指定一个特定的输入索引以从中获取属性值,该值必须小于的结果。 getNumInputs()。重要的是要注意,getAttr始终返回在Op的输入处看到的值。如果您想考虑自己或通过execOp调用的另一个Op已经完成的setAttrs,则必须使用getOutputAttr。

下图说明了此操作的一些微妙之处,最重要的是,execOp Op中的getAttr仅在查询位置为当前位置时才看到调用Op的结果,否则您将看到调用Op的'slot的输入在Op图表中。

getPotentialChildren()函数

FnAttribute::StringAttribute getPotentialChildren(     const std::string& inputLocationPath = std::string(),     int inputIndex = kFnKatGeolibDefaultInput) const;

场景图创建功能deleteSelf() 引入,指出这种呼叫的结果比它最初看起来要微妙得多。在评估上游Op并创建子代时,如果下游Op能够删除它们,则上游Op只能说明在某个子位置对下游Op进行评估后,其创建的子代可能存在。 。这是因为Op不了解在这样的位置进行评估时下游Op会做什么。在那种程度上getPotentialChildren()在Op的输入上返回给定位置的所有子项的列表。

prefetch()函数

void prefetch(const std::string& inputLocationPath = std::string(),               int inputIndex = kFnKatGeolibDefaultInput) const;

通过在Op的cook函数的代码中尽早使用prefetch()来保持良好的代码惯例。

prefetch()函数可用于指示场景图位置之间的依赖关系。例如:

/root/world/geo/a may call prefetch() for /root/world/geo/b because during the processing of /root/world/geo/a, attributes present at ./b将被要求。

CEL和实用程序

操作通常需要完成许多任务,例如:

创建位置层次结构,

确定是否应基于CEL表达式参数运行Op,

通过场景图向用户报告错误,以及

以易于使用的格式(例如边界框)获取众所周知的属性值。

当前,您可以在以下位置找到这些实用程序的标题:

$ KATANA_HOME / plugin_apis / include / FnGeolib / op / FnGeolibCookInterface.h

$ KATANA_HOME / plugin_apis / include / FnGeolib / util / *

该实用程序的实现位于:

$ KATANA_HOME / plugin_apis / src / FnGeolib / op / FnGeolibCookInterfaceUtils.cpp

$ KATANA_HOME / plugin_apis / src / FnGeolib / util / *

这些实用程序中的许多实用程序都是自记录的,并且遵循类似的模式。以下示例演示了如何使用CEL匹配实用程序:

// Should we run here? If not, return. FnAttribute::StringAttribute celAttr = interface.getOpArg("CEL"); if (!celAttr.isValid())     return; Foundry::Katana::MatchesCELInfo info; Foundry::Katana::MatchesCEL(info, interface, celAttr); if (!info.canMatchChildren) {     interface.stopChildTraversal(); } if (!info.matches)     return;

在上面的示例中,完成了几件事:

1.   我们确定CEL表达式是否可能与任何子代匹配,否则,我们指示运行时不要在子代位置评估此Op。
2.   我们确定是否应在此位置运行,如果没有,请尽早返回。

随意探索可用的实用程序功能的范围,因为在编写Ops时可以提高您的生产率。