1 // 2 // Copyright 2016 Pixar 3 // 4 // Licensed under the Apache License, Version 2.0 (the "Apache License") 5 // with the following modification; you may not use this file except in 6 // compliance with the Apache License and the following modification to it: 7 // Section 6. Trademarks. is deleted and replaced with: 8 // 9 // 6. Trademarks. This License does not grant permission to use the trade 10 // names, trademarks, service marks, or product names of the Licensor 11 // and its affiliates, except as required to comply with Section 4(c) of 12 // the License and to reproduce the content of the NOTICE file. 13 // 14 // You may obtain a copy of the Apache License at 15 // 16 // http://www.apache.org/licenses/LICENSE-2.0 17 // 18 // Unless required by applicable law or agreed to in writing, software 19 // distributed under the Apache License with the above modification is 20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 // KIND, either express or implied. See the Apache License for the specific 22 // language governing permissions and limitations under the Apache License. 23 // 24 #ifndef PXR_USD_USD_GEOM_XFORM_OP_H 25 #define PXR_USD_USD_GEOM_XFORM_OP_H 26 27 /// \file usdGeom/xformOp.h 28 29 #include "pxr/pxr.h" 30 #include "pxr/usd/usdGeom/api.h" 31 #include "pxr/usd/usd/attribute.h" 32 #include "pxr/usd/usd/attributeQuery.h" 33 #include "pxr/usd/usdGeom/tokens.h" 34 35 #include <string> 36 #include <vector> 37 #include <typeinfo> 38 39 #include <boost/variant.hpp> 40 41 #include "pxr/base/tf/staticTokens.h" 42 43 PXR_NAMESPACE_OPEN_SCOPE 44 45 46 /// \hideinitializer 47 #define USDGEOM_XFORM_OP_TYPES \ 48 (translate) \ 49 (scale) \ 50 (rotateX) \ 51 (rotateY) \ 52 (rotateZ) \ 53 (rotateXYZ) \ 54 (rotateXZY) \ 55 (rotateYXZ) \ 56 (rotateYZX) \ 57 (rotateZXY) \ 58 (rotateZYX) \ 59 (orient) \ 60 (transform) \ 61 ((resetXformStack, "!resetXformStack!")) 62 63 /// \anchor UsdGeomXformOpTypes 64 /// Provides TfToken's for use in conjunction with UsdGeomXformable::Add 65 /// XformOp() and UsdGeomXformOp::GetOpType(), to establish op type. 66 /// 67 /// The component operation names and their meanings are: 68 /// \li <b>translate</b> - XYZ translation 69 /// \li <b>scale</b> - XYZ scale 70 /// \li <b>rotateX</b> - rotation about the X axis, <b>in degrees</b> 71 /// \li <b>rotateY</b> - rotation about the Y axis, <b>in degrees</b> 72 /// \li <b>rotateZ</b> - rotation about the Z axis, <b>in degrees</b> 73 /// \li <b>rotateXYZ, rotateXZY, rotateYXZ, rotateYZX, rotateZXY, rotateZYX</b> 74 /// - a set of three canonical Euler rotations, packed into a single 75 /// Vec3, for conciseness and efficiency of reading. The \\em first 76 /// axis named is the most local, so a single rotateXYZ is equivalent to 77 /// the ordered ops "rotateZ rotateY rotateX". See also 78 /// \ref usdGeom_rotationPackingOrder "note on rotation packing order." 79 /// \li <b>orient</b> - arbitrary axis/angle rotation, expressed as a quaternion 80 /// \li <b>transform</b> - 4x4 matrix transformation 81 /// \li <b>resetXformStack</b> - when appearing as the first op, instructs 82 /// client that the transform stack should be cleared of any inherited 83 /// transformation prior to processing the rest of the prims ops. It is 84 /// an error for resetXformStack to appear anywhere other than as the 85 /// first element in \em xformOpOrder. 86 TF_DECLARE_PUBLIC_TOKENS(UsdGeomXformOpTypes, USDGEOM_API, USDGEOM_XFORM_OP_TYPES); 87 88 /// \class UsdGeomXformOp 89 /// 90 /// Schema wrapper for UsdAttribute for authoring and computing 91 /// transformation operations, as consumed by UsdGeomXformable schema. 92 /// 93 /// The semantics of an op are determined primarily by its name, which allows 94 /// us to decode an op very efficiently. All ops are independent attributes, 95 /// which must live in the "xformOp" property namespace. The op's primary name 96 /// within the namespace must be one of \ref UsdGeomXformOpTypes, which 97 /// determines the type of transformation operation, and its secondary name 98 /// (or suffix) within the namespace (which is not required to exist), can be 99 /// any name that distinguishes it from other ops of the same type. Suffixes 100 /// are generally imposed by higer level xform API schemas. 101 /// 102 /// \anchor usdGeom_rotationPackingOrder 103 /// \note 104 /// <b>On packing order of rotateABC triples</b><br> 105 /// The order in which the axis rotations are recorded in a Vec3* for the 106 /// six \em rotateABC Euler triples <b>is always the same:</b> vec[0] = X, 107 /// vec[1] = Y, vec[2] = Z . The \em A, \em B, \em C in the op name dictate 108 /// the order in which their corresponding elements are consumed by the 109 /// rotation, not how they are laid out. 110 /// 111 class UsdGeomXformOp 112 { 113 public: 114 115 /// Enumerates the set of all transformation operation types. 116 enum Type { 117 TypeInvalid, ///< Represents an invalid xformOp. 118 TypeTranslate, ///< XYZ translation. 119 TypeScale, ///< XYZ scale. 120 TypeRotateX, ///< Rotation about the X-axis, <b>in degrees</b>. 121 TypeRotateY, ///< Rotation about the Y-axis, <b>in degrees</b>. 122 TypeRotateZ, ///< Rotation about the Z-axis, <b>in degrees</b>. 123 TypeRotateXYZ, ///< Set of 3 canonical Euler rotations 124 /// \ref usdGeom_rotationPackingOrder "in XYZ order" 125 TypeRotateXZY, ///< Set of 3 canonical Euler rotations 126 /// \ref usdGeom_rotationPackingOrder "in XZY order" 127 TypeRotateYXZ, ///< Set of 3 canonical Euler rotations 128 /// \ref usdGeom_rotationPackingOrder "in YXZ order" 129 TypeRotateYZX, ///< Set of 3 canonical Euler rotations 130 /// \ref usdGeom_rotationPackingOrder "in YZX order" 131 TypeRotateZXY, ///< Set of 3 canonical Euler rotations 132 /// \ref usdGeom_rotationPackingOrder "in ZXY order" 133 TypeRotateZYX, ///< Set of 3 canonical Euler rotations 134 /// \ref usdGeom_rotationPackingOrder "in ZYX order" 135 TypeOrient, ///< Arbitrary axis/angle rotation, expressed as a quaternion. 136 TypeTransform ///< A 4x4 matrix transformation. 137 }; 138 139 /// Precision with which the value of the tranformation operation is encoded. 140 enum Precision { 141 PrecisionDouble, ///< Double precision 142 PrecisionFloat, ///< Floating-point precision 143 PrecisionHalf ///< Half-float precision 144 }; 145 146 // Default constructor returns an invalid XformOp. Exists for 147 // container classes UsdGeomXformOp()148 UsdGeomXformOp() 149 { 150 /* NOTHING */ 151 } 152 153 /// Speculative constructor that will produce a valid UsdGeomXformOp when 154 /// \p attr already represents an attribute that is XformOp, and 155 /// produces an \em invalid XformOp otherwise (i.e. 156 /// explicit-bool conversion operator will return false). 157 /// 158 /// Calling \c UsdGeomXformOp::IsXformOp(attr) will return the same truth 159 /// value as this constructor, but if you plan to subsequently use the 160 /// XformOp anyways, just use this constructor. 161 /// 162 /// \p isInverseOp is set to true to indicate an inverse transformation 163 /// op. 164 /// 165 /// This constructor exists mainly for internal use. Clients should use 166 /// AddXformOp API (or one of Add*Op convenience API) to create and retain 167 /// a copy of an UsdGeomXformOp object. 168 /// 169 USDGEOM_API 170 explicit UsdGeomXformOp(const UsdAttribute &attr, bool isInverseOp=false); 171 172 // ------------------------------------------------------- 173 /// \name Static Helper API 174 // ------------------------------------------------------- 175 176 /// Test whether a given UsdAttribute represents valid XformOp, which 177 /// implies that creating a UsdGeomXformOp from the attribute will succeed. 178 /// 179 /// Success implies that \c attr.IsDefined() is true. 180 USDGEOM_API 181 static bool IsXformOp(const UsdAttribute &attr); 182 183 /// Test whether a given attrbute name represents a valid XformOp, which 184 /// implies that creating a UsdGeomXformOp from the corresponding 185 /// UsdAttribute will succeed. 186 /// 187 USDGEOM_API 188 static bool IsXformOp(const TfToken &attrName); 189 190 /// Returns the TfToken used to encode the given \p opType. 191 /// Note that an empty TfToken is used to represent TypeInvalid 192 USDGEOM_API 193 static TfToken const &GetOpTypeToken(Type const opType); 194 195 /// Returns the Type enum associated with the given \p opTypeToken. 196 USDGEOM_API 197 static Type GetOpTypeEnum(TfToken const &opTypeToken); 198 199 /// Returns the precision corresponding to the given value typeName. 200 USDGEOM_API 201 static Precision GetPrecisionFromValueTypeName(const SdfValueTypeName& typeName); 202 203 /// Returns the value typeName token that corresponds to the given 204 /// combination of \p opType and \p precision. 205 USDGEOM_API 206 static const SdfValueTypeName &GetValueTypeName(const Type opType, 207 const Precision precision); 208 209 /// Returns the xformOp's name as it appears in xformOpOrder, given 210 /// the opType, the (optional) suffix and whether it is an inverse 211 /// operation. 212 USDGEOM_API 213 static TfToken GetOpName(const Type opType, 214 const TfToken &opSuffix=TfToken(), 215 bool inverse=false); 216 217 // ------------------------------------------------------- 218 /// \name Data Encoding Queries 219 // ------------------------------------------------------- 220 221 /// Return the operation type of this op, one of \ref UsdGeomXformOp::Type GetOpType()222 Type GetOpType() const { 223 return _opType; 224 } 225 226 /// Returns the precision level of the xform op. 227 USDGEOM_API 228 Precision GetPrecision() const; 229 230 /// Returns whether the xformOp represents an inverse operation. IsInverseOp()231 bool IsInverseOp() const { 232 return _isInverseOp; 233 } 234 235 /// Returns the opName as it appears in the xformOpOrder attribute. 236 /// 237 /// This will begin with "!invert!:xformOp:" if it is an inverse xform 238 /// operation. If it is not an inverse xformOp, it will begin with 'xformOp:'. 239 /// 240 /// This will be empty for an invalid xformOp. 241 /// 242 USDGEOM_API 243 TfToken GetOpName() const; 244 245 /// Does this op have the given suffix in its name. 246 USDGEOM_API 247 bool HasSuffix(TfToken const &suffix) const; 248 249 // --------------------------------------------------------------- 250 /// \name Computing with Ops 251 // --------------------------------------------------------------- 252 253 /// We allow ops to be encoded with varying degrees of precision, 254 /// depending on the clients needs and constraints. GetAs() will 255 /// attempt to convert the stored data to the requested datatype. 256 /// 257 /// Note this accessor incurs some overhead beyond Get()'ing the 258 /// value as a VtValue and dealing with the results yourself. 259 /// 260 /// \return true if a value was successfully read \em and converted 261 /// to the requested datatype (see \ref VtValue::Cast()), false 262 /// otherwise. A problem reading or failure to convert will cause 263 /// an error to be emitted. 264 /// 265 /// \note the requested type \p T must be constructable by assignment 266 template <typename T> GetAs(T * value,UsdTimeCode time)267 bool GetAs(T* value, UsdTimeCode time) const { 268 VtValue v; 269 if (!Get(&v, time)) { 270 return false; 271 } 272 v.Cast<T>(); 273 if (v.IsEmpty()){ 274 TfType thisType = GetTypeName().GetType(); 275 TF_CODING_ERROR("Unable to convert xformOp %s's value from %s to " 276 "requested type %s.", GetAttr().GetPath().GetText(), 277 thisType.GetTypeName().c_str(), 278 TfType::GetCanonicalTypeName(typeid(*value)).c_str()); 279 return false; 280 } 281 *value = v.UncheckedGet<T>(); 282 return true; 283 } 284 285 /// Return the 4x4 matrix that applies the transformation encoded 286 /// by op \p opType and data value \p opVal. 287 /// 288 /// If \p isInverseOp is true, then the inverse of the tranformation 289 /// represented by the op/value pair is returned. 290 /// 291 /// An error will be issued if \p opType is not one of the values in the enum 292 /// \ref UsdGeomXformOp::Type or if \p opVal cannot be converted 293 /// to a suitable input to \p opType 294 USDGEOM_API 295 static GfMatrix4d GetOpTransform(Type const opType, 296 VtValue const &opVal, 297 bool isInverseOp=false); 298 299 300 /// Return the 4x4 matrix that applies the transformation encoded 301 /// in this op at \p time. 302 /// 303 /// Returns the identity matrix and issues a coding error if the op is 304 /// invalid. 305 /// 306 /// If the op is valid, but has no authored value, the identity 307 /// matrix is returned and no error is issued. 308 /// 309 USDGEOM_API 310 GfMatrix4d GetOpTransform(UsdTimeCode time) const; 311 312 /// Determine whether there is any possibility that this op's value 313 /// may vary over time. 314 /// 315 /// The determination is based on a snapshot of the authored state of the 316 /// op, and may become invalid in the face of further authoring. MightBeTimeVarying()317 bool MightBeTimeVarying() const { 318 return boost::apply_visitor(_GetMightBeTimeVarying(), _attr); 319 } 320 321 // --------------------------------------------------------------- 322 /// \name UsdAttribute API 323 // --------------------------------------------------------------- 324 325 /// Allow UsdGeomXformOp to auto-convert to UsdAttribute, so you can 326 /// pass a UsdGeomXformOp to any function that accepts a UsdAttribute or 327 /// const-ref thereto. 328 operator UsdAttribute const& () const { return GetAttr(); } 329 330 /// Explicit UsdAttribute extractor GetAttr()331 UsdAttribute const &GetAttr() const { 332 return boost::apply_visitor(_GetAttr(), _attr); 333 } 334 335 /// Return true if the wrapped UsdAttribute::IsDefined(), and in 336 /// addition the attribute is identified as a XformOp. IsDefined()337 bool IsDefined() const { return IsXformOp(GetAttr()); } 338 339 public: 340 /// \anchor UsdGeomXformOp_explicit_bool 341 /// Explicit bool conversion operator. An XformOp object converts to 342 /// \c true iff it is valid for querying and authoring values and metadata, 343 /// (which is identically equivalent to IsDefined()), and converts to 344 /// \c false otherwise. 345 explicit operator bool() const { 346 return IsDefined(); 347 } 348 349 /// Equality comparison. Return true if \a lhs and \a rhs represent the 350 /// same underlying UsdAttribute, false otherwise. 351 friend bool operator==(const UsdGeomXformOp &lhs, 352 const UsdGeomXformOp &rhs) { 353 return lhs.GetAttr() == rhs.GetAttr(); 354 } 355 356 /// Inequality comparison. Return false if \a lhs and \a rhs represent the 357 /// same underlying UsdAttribute, true otherwise. 358 friend bool operator!=(const UsdGeomXformOp &lhs, 359 const UsdGeomXformOp &rhs) { 360 return !(lhs == rhs); 361 } 362 363 /// \sa UsdAttribute::GetName() GetName()364 TfToken const &GetName() const { return GetAttr().GetName(); } 365 366 /// \sa UsdAttribute::GetBaseName() GetBaseName()367 TfToken GetBaseName() const { return GetAttr().GetBaseName(); } 368 369 /// \sa UsdAttribute::GetNamespace() GetNamespace()370 TfToken GetNamespace() const { return GetAttr().GetNamespace(); } 371 372 /// \sa UsdAttribute::SplitName() SplitName()373 std::vector<std::string> SplitName() const { return GetAttr().SplitName(); }; 374 375 /// \sa UsdAttribute::GetTypeName() GetTypeName()376 SdfValueTypeName GetTypeName() const { return GetAttr().GetTypeName(); } 377 378 /// Get the attribute value of the XformOp at \p time. 379 /// 380 /// \note For inverted ops, this returns the raw, uninverted value. 381 /// 382 template <typename T> 383 bool Get(T* value, UsdTimeCode time = UsdTimeCode::Default()) const { 384 return boost::apply_visitor(_Get<T>(value, time), _attr); 385 } 386 387 /// Set the attribute value of the XformOp at \p time 388 /// 389 /// \note This only works on non-inverse operations. If invoked on 390 /// an inverse xform operation, a coding error is issued and no value is 391 /// authored. 392 /// 393 template <typename T> 394 bool Set(T const & value, UsdTimeCode time = UsdTimeCode::Default()) const { 395 // Issue a coding error and return without setting value, 396 // if this is an inverse op. 397 if (_isInverseOp) { 398 TF_CODING_ERROR("Cannot set a value on the inverse xformOp '%s'. " 399 "Please set value on the paired non-inverse xformOp instead.", 400 GetOpName().GetText()); 401 return false; 402 } 403 404 return GetAttr().Set(value, time); 405 } 406 407 /// Populates the list of time samples at which the associated attribute 408 /// is authored. GetTimeSamples(std::vector<double> * times)409 bool GetTimeSamples(std::vector<double> *times) const { 410 return boost::apply_visitor(_GetTimeSamples(times), _attr); 411 } 412 413 /// Populates the list of time samples within the given \p interval, 414 /// at which the associated attribute is authored. GetTimeSamplesInInterval(const GfInterval & interval,std::vector<double> * times)415 bool GetTimeSamplesInInterval(const GfInterval &interval, 416 std::vector<double> *times) const { 417 return boost::apply_visitor( 418 _GetTimeSamplesInInterval(interval, times), _attr); 419 } 420 421 /// Returns the number of time samples authored for this xformOp. GetNumTimeSamples()422 size_t GetNumTimeSamples() const { 423 return boost::apply_visitor(_GetNumTimeSamples(), _attr); 424 } 425 426 private: 427 struct _ValidAttributeTagType {}; 428 429 public: 430 // Allow clients that guarantee \p attr is valid avoid having 431 // UsdGeomXformOp's ctor check again. 432 USDGEOM_API 433 UsdGeomXformOp(const UsdAttribute &attr, bool isInverseOp, 434 _ValidAttributeTagType); 435 USDGEOM_API 436 UsdGeomXformOp(UsdAttributeQuery &&query, bool isInverseOp, 437 _ValidAttributeTagType); 438 private: 439 friend class UsdGeomXformable; 440 441 // Shared initialization function. 442 void _Init(); 443 444 // Return the op-type for the string value \p str. 445 static Type _GetOpTypeEnumFromCString(char const *str, size_t len); 446 447 // Returns the attribute belonging to \p prim that corresponds to the 448 // given \p opName. It also populates the output parameter \p isInverseOp 449 // appropriately. 450 // 451 // The attribute that's returned will be invalid if the 452 // corresponding xformOp attribute doesn't exist on the prim. 453 // 454 static UsdAttribute _GetXformOpAttr(UsdPrim const& prim, 455 const TfToken &opName, bool *isInverseOp); 456 457 // Private method for creating and using an attribute query interally for 458 // this xformOp. _CreateAttributeQuery()459 void _CreateAttributeQuery() const { 460 _attr = UsdAttributeQuery(GetAttr()); 461 } 462 463 // Factory for UsdGeomXformable's use, so that we can encapsulate the 464 // logic of what discriminates XformOp in this class, while 465 // preserving the pattern that attributes can only be created 466 // via their container objects. 467 // 468 // \p opType must be one of UsdGeomXformOp::Type 469 // 470 // \p precision must be one of UsdGeomXformOp::Precision. 471 // 472 // \return an invalid UsdGeomXformOp if we failed to create a valid 473 // attribute, a valid UsdGeomXformOp otherwise. It is not an 474 // error to create over an existing, compatible attribute. 475 // 476 // It is a failed verification for \p prim to be invalid/expired 477 // 478 // \sa UsdPrim::CreateAttribute() 479 UsdGeomXformOp(UsdPrim const& prim, Type const opType, 480 Precision const precision, TfToken const &opSuffix=TfToken(), 481 bool inverse=false); 482 483 // UsdAttributeQuery already contains a copy of the associated UsdAttribute. 484 // To minimize the memory usage, we only store one or the other. 485 // 486 // The lifetime of a UsdAttributeQuery needs to be managed very carefully as 487 // it gets invalidated whenever the associated attribute is authored. 488 // Hence, access to the creation of an attribute query is restricted inside 489 // a private member function named _CreateAttributeQuery(). 490 // 491 mutable boost::variant<UsdAttribute, UsdAttributeQuery> _attr; 492 493 Type _opType; 494 bool _isInverseOp; 495 496 // Visitor for getting xformOp value. 497 template <class T> 498 struct _Get : public boost::static_visitor<bool> 499 { 500 _Get(T *value_, value_Get501 UsdTimeCode time_ = UsdTimeCode::Default()) : value (value_), time(time_) 502 {} 503 operator_Get504 bool operator()(const UsdAttribute &attr) const 505 { 506 return attr.Get(value, time); 507 } 508 operator_Get509 bool operator()(const UsdAttributeQuery &attrQuery) const 510 { 511 return attrQuery.Get(value, time); 512 } 513 514 T *value; 515 UsdTimeCode time; 516 }; 517 518 // Visitor for getting a const-reference to the UsdAttribute. 519 struct _GetAttr : public boost::static_visitor<const UsdAttribute &> { 520 _GetAttr_GetAttr521 _GetAttr() {} 522 operator_GetAttr523 const UsdAttribute &operator()(const UsdAttribute &attr) const 524 { 525 return attr; 526 } 527 operator_GetAttr528 const UsdAttribute &operator()(const UsdAttributeQuery &attrQuery) const 529 { 530 return attrQuery.GetAttribute(); 531 } 532 }; 533 534 // Visitor for getting all the time samples. 535 struct _GetTimeSamples : public boost::static_visitor<bool> { 536 _GetTimeSamples_GetTimeSamples537 _GetTimeSamples(std::vector<double> *times_) : times(times_) {} 538 operator_GetTimeSamples539 bool operator()(const UsdAttribute &attr) const 540 { 541 return attr.GetTimeSamples(times); 542 } 543 operator_GetTimeSamples544 bool operator()(const UsdAttributeQuery &attrQuery) const 545 { 546 return attrQuery.GetTimeSamples(times); 547 } 548 549 std::vector<double> *times; 550 }; 551 552 // Visitor for getting all the time samples within a given interval. 553 struct _GetTimeSamplesInInterval : public boost::static_visitor<bool> { 554 _GetTimeSamplesInInterval_GetTimeSamplesInInterval555 _GetTimeSamplesInInterval(const GfInterval &interval_, 556 std::vector<double> *times_) 557 : interval(interval_), times(times_) 558 {} 559 operator_GetTimeSamplesInInterval560 bool operator()(const UsdAttribute &attr) const 561 { 562 return attr.GetTimeSamplesInInterval(interval, times); 563 } 564 operator_GetTimeSamplesInInterval565 bool operator()(const UsdAttributeQuery &attrQuery) const 566 { 567 return attrQuery.GetTimeSamplesInInterval(interval, times); 568 } 569 570 const GfInterval &interval; 571 std::vector<double> *times; 572 }; 573 574 // Visitor for getting the number of time samples. 575 struct _GetNumTimeSamples : public boost::static_visitor<size_t> { 576 _GetNumTimeSamples_GetNumTimeSamples577 _GetNumTimeSamples() {} 578 operator_GetNumTimeSamples579 size_t operator()(const UsdAttribute &attr) const 580 { 581 return attr.GetNumTimeSamples(); 582 } 583 operator_GetNumTimeSamples584 size_t operator()(const UsdAttributeQuery &attrQuery) const 585 { 586 return attrQuery.GetNumTimeSamples(); 587 } 588 }; 589 590 // Visitor for determining whether the op might vary over time. 591 struct _GetMightBeTimeVarying : public boost::static_visitor<bool> { 592 _GetMightBeTimeVarying_GetMightBeTimeVarying593 _GetMightBeTimeVarying() {} 594 operator_GetMightBeTimeVarying595 bool operator()(const UsdAttribute &attr) const 596 { 597 return attr.ValueMightBeTimeVarying(); 598 } 599 operator_GetMightBeTimeVarying600 bool operator()(const UsdAttributeQuery &attrQuery) const 601 { 602 return attrQuery.ValueMightBeTimeVarying(); 603 } 604 }; 605 606 }; 607 608 609 610 PXR_NAMESPACE_CLOSE_SCOPE 611 612 #endif // USD_XFORMOP_H 613