diff --git a/lib/flowViewport/sceneIndex/fvpReprSelectorSceneIndex.cpp b/lib/flowViewport/sceneIndex/fvpReprSelectorSceneIndex.cpp index b4081fe41..7dca75455 100644 --- a/lib/flowViewport/sceneIndex/fvpReprSelectorSceneIndex.cpp +++ b/lib/flowViewport/sceneIndex/fvpReprSelectorSceneIndex.cpp @@ -24,7 +24,7 @@ #include #include #include - +#include // This class is a filtering scene index that that applies a different RepSelector on geometries (such as wireframe or wireframe on shaded) // and also applies an overrideWireframecolor for HdStorm @@ -73,13 +73,17 @@ const HdRetainedContainerDataSourceHandle sWireframeDisplayStyleDataSource }//End of namespace -ReprSelectorSceneIndex::ReprSelectorSceneIndex(const HdSceneIndexBaseRefPtr& inputSceneIndex, RepSelectorType type, const std::shared_ptr& wireframeColorInterface) +ReprSelectorSceneIndex::ReprSelectorSceneIndex(const HdSceneIndexBaseRefPtr& inputSceneIndex, const std::shared_ptr& wireframeColorInterface) : ParentClass(inputSceneIndex), InputSceneIndexUtils(inputSceneIndex), _wireframeColorInterface(wireframeColorInterface) { TF_AXIOM(_wireframeColorInterface); - switch (type){ +} + +void ReprSelectorSceneIndex::SetReprType(RepSelectorType reprType, bool needsReprChanged, int refineLevel) +{ + switch (reprType){ case RepSelectorType::WireframeRefined: _wireframeTypeDataSource = sWireframeDisplayStyleDataSource; break; @@ -87,16 +91,41 @@ ReprSelectorSceneIndex::ReprSelectorSceneIndex(const HdSceneIndexBaseRefPtr& inp _wireframeTypeDataSource = sdWireframeOnShadedDisplayStyleDataSource; break; case RepSelectorType::WireframeOnSurfaceRefined: - _wireframeTypeDataSource = sRefinedWireframeOnShadedDisplayStyleDataSource; + if (refineLevel > 0) + _wireframeTypeDataSource = sRefinedWireframeOnShadedDisplayStyleDataSource; + else + _wireframeTypeDataSource = sdWireframeOnShadedDisplayStyleDataSource; + break; + case RepSelectorType::Default: break; } + + const HdDataSourceLocatorSet locators{ + HdLegacyDisplayStyleSchema::GetDefaultLocator(), + HdPrimvarsSchema::GetDefaultLocator(), + HdLegacyDisplayStyleSchema::GetDefaultLocator() + }; + _needsReprChanged = needsReprChanged; + _DirtyAllPrims(locators); +} + +void +ReprSelectorSceneIndex::_DirtyAllPrims( + const HdDataSourceLocatorSet locators) +{ + HdSceneIndexObserver::DirtiedPrimEntries entries; + for (const SdfPath &path : HdSceneIndexPrimView(GetInputSceneIndex())) { + entries.push_back({path, locators}); + } + _SendPrimsDirtied(entries); } HdSceneIndexPrim ReprSelectorSceneIndex::GetPrim(const SdfPath& primPath) const { HdSceneIndexPrim prim = GetInputSceneIndex()->GetPrim(primPath); - if (prim.dataSource && !_isExcluded(primPath) && (prim.primType == HdPrimTypeTokens->mesh) ){ - + if ( (prim.dataSource && !_isExcluded(primPath)) && + (prim.primType == HdPrimTypeTokens->mesh) && _needsReprChanged ) { + //Edit the dataSource as an overlay will not replace any existing attribute value. // So we need to edit the _primVarsTokens->overrideWireframeColor attribute as they may already exist in the prim auto edited = HdContainerDataSourceEditor(prim.dataSource); @@ -111,10 +140,8 @@ HdSceneIndexPrim ReprSelectorSceneIndex::GetPrim(const SdfPath& primPath) const //Edit the cull style edited.Set(HdLegacyDisplayStyleSchema::GetCullStyleLocator(), HdRetainedTypedSampledDataSource::New(HdCullStyleTokens->nothing));//No culling - prim.dataSource = HdOverlayContainerDataSource::New({ edited.Finish(), _wireframeTypeDataSource}); } - return prim; } diff --git a/lib/flowViewport/sceneIndex/fvpReprSelectorSceneIndex.h b/lib/flowViewport/sceneIndex/fvpReprSelectorSceneIndex.h index 632ae2cb1..6a50fe8ca 100644 --- a/lib/flowViewport/sceneIndex/fvpReprSelectorSceneIndex.h +++ b/lib/flowViewport/sceneIndex/fvpReprSelectorSceneIndex.h @@ -47,11 +47,12 @@ class ReprSelectorSceneIndex : public PXR_NS::HdSingleInputFilteringSceneIndexBa WireframeRefined, //Refined wireframe (refined means that it supports a "refineLevel" attribute in the displayStyle to get a more refined drawing, valid range is from 0 to 8) WireframeOnSurface, //Wireframe on surface not refined WireframeOnSurfaceRefined,//Wireframe on surface refined + Default, // Default shaded }; FVP_API - static ReprSelectorSceneIndexRefPtr New(const PXR_NS::HdSceneIndexBaseRefPtr& inputSceneIndex, RepSelectorType type, const std::shared_ptr& wireframeColorInterface){ - return PXR_NS::TfCreateRefPtr(new ReprSelectorSceneIndex(inputSceneIndex, type, wireframeColorInterface)); + static ReprSelectorSceneIndexRefPtr New(const PXR_NS::HdSceneIndexBaseRefPtr& inputSceneIndex, const std::shared_ptr& wireframeColorInterface){ + return PXR_NS::TfCreateRefPtr(new ReprSelectorSceneIndex(inputSceneIndex, wireframeColorInterface)); } // From HdSceneIndexBase @@ -70,10 +71,13 @@ class ReprSelectorSceneIndex : public PXR_NS::HdSingleInputFilteringSceneIndexBa void addExcludedSceneRoot(const PXR_NS::SdfPath& sceneRoot) { _excludedSceneRoots.emplace(sceneRoot); } + + FVP_API + void SetReprType(RepSelectorType reprType, bool needsReprChanged, int refineLevel); protected: -ReprSelectorSceneIndex(const PXR_NS::HdSceneIndexBaseRefPtr& inputSceneIndex, RepSelectorType type, const std::shared_ptr& wireframeColorInterface); +ReprSelectorSceneIndex(const PXR_NS::HdSceneIndexBaseRefPtr& inputSceneIndex, const std::shared_ptr& wireframeColorInterface); //From HdSingleInputFilteringSceneIndexBase void _PrimsAdded(const PXR_NS::HdSceneIndexBase& sender, const PXR_NS::HdSceneIndexObserver::AddedPrimEntries& entries) override{ @@ -99,6 +103,9 @@ ReprSelectorSceneIndex(const PXR_NS::HdSceneIndexBaseRefPtr& inputSceneIndex, Re } std::set _excludedSceneRoots; + + void _DirtyAllPrims(const PXR_NS::HdDataSourceLocatorSet locators); + bool _needsReprChanged {false}; PXR_NS::HdRetainedContainerDataSourceHandle _wireframeTypeDataSource = nullptr; std::shared_ptr _wireframeColorInterface; diff --git a/lib/mayaHydra/mayaPlugin/renderOverride.cpp b/lib/mayaHydra/mayaPlugin/renderOverride.cpp index 2cda60812..3ef360c07 100644 --- a/lib/mayaHydra/mayaPlugin/renderOverride.cpp +++ b/lib/mayaHydra/mayaPlugin/renderOverride.cpp @@ -684,7 +684,6 @@ MStatus MtohRenderOverride::Render( delegateParams.displaySmoothMeshes = !(currentDisplayStyle & MHWRender::MFrameContext::kFlatShaded); const bool currentUseDefaultMaterial = (drawContext.getDisplayStyle() & MHWRender::MFrameContext::kDefaultMaterial); - const bool xRayEnabled = (drawContext.getDisplayStyle() & MHWRender::MFrameContext::kXray); if (_mayaHydraSceneIndex) { _mayaHydraSceneIndex->SetDefaultLightEnabled(_hasDefaultLighting); @@ -692,7 +691,7 @@ MStatus MtohRenderOverride::Render( _mayaHydraSceneIndex->SetParams(delegateParams); _mayaHydraSceneIndex->PreFrame(drawContext); - if (_NeedToRecreateTheSceneIndicesChain(currentDisplayStyle, xRayEnabled)){ + if (_NeedToRecreateTheSceneIndicesChain(currentDisplayStyle)){ _blockPrimRemovalPropagationSceneIndex->setPrimRemovalBlocked(true);//Prevent prim removal propagation to keep the current selection. //We need to recreate the filtering scene index chain after the merging scene index as there was a change such as in the BBox display style which has been turned on or off. _lastFilteringSceneIndexBeforeCustomFiltering = nullptr;//Release @@ -710,8 +709,6 @@ MStatus MtohRenderOverride::Render( } const Fvp::InformationInterface::ViewportInformation hydraViewportInformation(std::string(panelName.asChar()), cameraName); manager.AddViewportInformation(hydraViewportInformation, _renderIndexProxy, _lastFilteringSceneIndexBeforeCustomFiltering); - - _xRayEnabled = xRayEnabled; _blockPrimRemovalPropagationSceneIndex->setPrimRemovalBlocked(false);//Allow prim removal propagation again. } } @@ -738,6 +735,35 @@ MStatus MtohRenderOverride::Render( _defaultMaterialSceneIndex->Enable(currentUseDefaultMaterial); _useDefaultMaterial = currentUseDefaultMaterial; } + + // Set Required Hydra Repr (Wireframe/WireframeOnShaded/Shaded) + // Hydra supports Wireframe and WireframeOnSurfaceRefined repr for wireframe on shaded mode. + // Refinement level for Hydra is set in Hydra Render Globals + const MFrameContext::WireOnShadedMode wireOnShadedMode = MFrameContext::wireOnShadedMode();//Get the user preference + if ( (_reprSelectorSceneIndex && (currentDisplayStyle != _oldDisplayStyle) ) || (delegateParams.refineLevel != _oldRefineLevel)){ + if( (currentDisplayStyle & MHWRender::MFrameContext::kWireFrame) && + ((currentDisplayStyle & MHWRender::MFrameContext::kGouraudShaded) || + (currentDisplayStyle & MHWRender::MFrameContext::kTextured)) ) { + // Wireframe on top of shaded + if (MFrameContext::WireOnShadedMode::kWireframeOnShadedFull == wireOnShadedMode) { + _reprSelectorSceneIndex->SetReprType(Fvp::ReprSelectorSceneIndex::RepSelectorType::WireframeOnSurfaceRefined, + /*needsReprChanged=*/true, delegateParams.refineLevel); + } else { + _reprSelectorSceneIndex->SetReprType(Fvp::ReprSelectorSceneIndex::RepSelectorType::WireframeOnSurface, + /*needsReprChanged=*/true, delegateParams.refineLevel); + } + } + else if( (currentDisplayStyle & MHWRender::MFrameContext::kWireFrame) ) { + //wireframe only, not on top of shaded + _reprSelectorSceneIndex->SetReprType(Fvp::ReprSelectorSceneIndex::RepSelectorType::WireframeRefined, + /*needsReprChanged=*/true, delegateParams.refineLevel); + } + else // Shaded mode + _reprSelectorSceneIndex->SetReprType(Fvp::ReprSelectorSceneIndex::RepSelectorType::Default, + /*needsReprChanged=*/false, delegateParams.refineLevel); + + _oldRefineLevel = delegateParams.refineLevel; + } HdxRenderTaskParams params; params.enableLighting = true; @@ -1051,7 +1077,8 @@ void MtohRenderOverride::ClearHydraResources(bool fullReset) _selection.reset(); _wireframeColorInterfaceImp.reset(); _leadObjectPathTracker.reset(); - + _oldDisplayStyle = 0; + _oldRefineLevel = 0; // Cleanup internal context data that keep references to data that is now // invalid. _engine.ClearTaskContextData(); @@ -1107,16 +1134,11 @@ void MtohRenderOverride::_CreateSceneIndicesChainAfterMergingSceneIndex(const MH _mayaHydraSceneIndex ? _mayaHydraSceneIndex->GetDefaultMaterialExclusionPaths(): SdfPathVector()); const unsigned int currentDisplayStyle = drawContext.getDisplayStyle(); - const MFrameContext::WireOnShadedMode wireOnShadedMode = MFrameContext::wireOnShadedMode();//Get the user preference auto mergingSceneIndex = _renderIndexProxy->GetMergingSceneIndex(); if(! _leadObjectPathTracker){ _leadObjectPathTracker = std::make_shared(mergingSceneIndex, _dirtyLeadObjectSceneIndex); } - - if (! _wireframeColorInterfaceImp){ - _wireframeColorInterfaceImp = std::make_shared(_selection, _leadObjectPathTracker); - } //Are we using Bounding Box display style ? if (currentDisplayStyle & MHWRender::MFrameContext::kBoundingBox){ @@ -1125,36 +1147,17 @@ void MtohRenderOverride::_CreateSceneIndicesChainAfterMergingSceneIndex(const MH bboxSceneIndex->addExcludedSceneRoot(MAYA_NATIVE_ROOT); // Maya native prims are already converted by OGS _lastFilteringSceneIndexBeforeCustomFiltering = bboxSceneIndex; } - else if (currentDisplayStyle & MHWRender::MFrameContext::kWireFrame){//Are we using wireframe somehow ? - - if( (currentDisplayStyle & MHWRender::MFrameContext::kGouraudShaded) || (currentDisplayStyle & MHWRender::MFrameContext::kTextured)){ - // Wireframe on top of shaded - //Reduced quality - if (MFrameContext::WireOnShadedMode::kWireFrameOnShadedReduced == wireOnShadedMode ){ - //Insert the reprselector filtering scene index which updates the repr selector on geometries - auto reprSelectorSceneIndex = Fvp::ReprSelectorSceneIndex::New(_lastFilteringSceneIndexBeforeCustomFiltering, - Fvp::ReprSelectorSceneIndex::RepSelectorType::WireframeOnSurface, _wireframeColorInterfaceImp); - reprSelectorSceneIndex->addExcludedSceneRoot(MAYA_NATIVE_ROOT); // Maya native prims are already converted by OGS - _lastFilteringSceneIndexBeforeCustomFiltering = reprSelectorSceneIndex; - } else {//Full quality - //Should we support kWireFrameOnShadedNone and do not display any wireframe ? - //Insert the reprselector filtering scene index which updates the repr selector on geometries - auto reprSelectorSceneIndex = Fvp::ReprSelectorSceneIndex::New(_lastFilteringSceneIndexBeforeCustomFiltering, - Fvp::ReprSelectorSceneIndex::RepSelectorType::WireframeOnSurfaceRefined, _wireframeColorInterfaceImp); - reprSelectorSceneIndex->addExcludedSceneRoot(MAYA_NATIVE_ROOT); // Maya native prims are already converted by OGS - _lastFilteringSceneIndexBeforeCustomFiltering = reprSelectorSceneIndex; - } - } - else{ - //wireframe only, not on top of shaded - - //Insert the reprselector filtering scene index which updates the repr selector on geometries - auto reprSelectorSceneIndex = Fvp::ReprSelectorSceneIndex::New(_lastFilteringSceneIndexBeforeCustomFiltering, - Fvp::ReprSelectorSceneIndex::RepSelectorType::WireframeRefined, _wireframeColorInterfaceImp); - reprSelectorSceneIndex->addExcludedSceneRoot(MAYA_NATIVE_ROOT); // Maya native prims are already converted by OGS - _lastFilteringSceneIndexBeforeCustomFiltering = reprSelectorSceneIndex; - } + + if (! _wireframeColorInterfaceImp){ + _wireframeColorInterfaceImp = std::make_shared(_selection, _leadObjectPathTracker); } + + // Repr selector Scene Index + _lastFilteringSceneIndexBeforeCustomFiltering = _reprSelectorSceneIndex = + Fvp::ReprSelectorSceneIndex::New(_lastFilteringSceneIndexBeforeCustomFiltering, + _wireframeColorInterfaceImp); + _reprSelectorSceneIndex->addExcludedSceneRoot(MAYA_NATIVE_ROOT); + _reprSelectorSceneIndex->SetReprType(Fvp::ReprSelectorSceneIndex::RepSelectorType::Default, false, _globals.delegateParams.refineLevel); _wireframeSelectionHighlightSceneIndex = TfDynamic_cast(Fvp::WireframeSelectionHighlightSceneIndex::New(_lastFilteringSceneIndexBeforeCustomFiltering, _selection, _wireframeColorInterfaceImp)); _wireframeSelectionHighlightSceneIndex->SetDisplayName("Flow Viewport Wireframe Selection Highlight Scene Index"); @@ -1636,17 +1639,10 @@ void MtohRenderOverride::_RenderOverrideChangedCallback( } // return true if we need to recreate the filtering scene indices chain because of a change, false otherwise. -bool MtohRenderOverride::_NeedToRecreateTheSceneIndicesChain(unsigned int currentDisplayStyle, bool xRayEnabled) +bool MtohRenderOverride::_NeedToRecreateTheSceneIndicesChain(unsigned int currentDisplayStyle) { - if (areDifferentForOneOfTheseBits(currentDisplayStyle, _oldDisplayStyle, - MHWRender::MFrameContext::kGouraudShaded | - MHWRender::MFrameContext::kWireFrame | - MHWRender::MFrameContext::kBoundingBox ) - ){ - return true; - } - - if (_xRayEnabled != xRayEnabled){ + if (areDifferentForOneOfTheseBits(currentDisplayStyle, _oldDisplayStyle, + MHWRender::MFrameContext::kBoundingBox)){ return true; } diff --git a/lib/mayaHydra/mayaPlugin/renderOverride.h b/lib/mayaHydra/mayaPlugin/renderOverride.h index f6ec3f57d..19cc3fb9a 100644 --- a/lib/mayaHydra/mayaPlugin/renderOverride.h +++ b/lib/mayaHydra/mayaPlugin/renderOverride.h @@ -49,6 +49,7 @@ #include #include #include +#include #include #include @@ -209,7 +210,7 @@ class MtohRenderOverride : public MHWRender::MRenderOverride, void _AddPluginSelectionHighlighting(); - bool _NeedToRecreateTheSceneIndicesChain(unsigned int currentDisplayStyle, bool xRayEnabled); + bool _NeedToRecreateTheSceneIndicesChain(unsigned int currentDisplayStyle); // Determine the pick handler which should handle a pick hit, to transform // the pick hit into a selection. @@ -260,6 +261,7 @@ class MtohRenderOverride : public MHWRender::MRenderOverride, HdSceneIndexBaseRefPtr _inputSceneIndexOfFilteringSceneIndicesChain {nullptr}; Fvp::DisplayStyleOverrideSceneIndexRefPtr _displayStyleSceneIndex; Fvp::PruneTexturesSceneIndexRefPtr _pruneTexturesSceneIndex; + Fvp::ReprSelectorSceneIndexRefPtr _reprSelectorSceneIndex; Fvp::DefaultMaterialSceneIndexRefPtr _defaultMaterialSceneIndex; HdRenderIndex* _renderIndex = nullptr; Fvp::SelectionTrackerSharedPtr _fvpSelectionTracker; @@ -312,6 +314,7 @@ class MtohRenderOverride : public MHWRender::MRenderOverride, bool _hasDefaultLighting = false; bool _currentlyTextured = false; unsigned int _oldDisplayStyle {0}; + int _oldRefineLevel {0}; bool _useDefaultMaterial; bool _xRayEnabled; }; diff --git a/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt b/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt index 560ea1939..db3a5e7b7 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt +++ b/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt @@ -75,6 +75,7 @@ set(INTERACTIVE_TEST_SCRIPT_FILES cpp/testPointInstancingWireframeHighlight.py cpp/testGeomSubsetsPicking.py cpp/testSinglePicking.py + cpp/testSceneIndexDirtying.py cpp/testGeomSubsetsWireframeHighlight.py ) diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt b/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt index 24de6afc0..430cfbc9a 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt @@ -46,6 +46,7 @@ target_sources(${TARGET_NAME} testPointInstancingWireframeHighlight.cpp testGeomSubsetsPicking.cpp testSinglePicking.cpp + testSceneIndexDirtying.cpp testGeomSubsetsWireframeHighlight.cpp ) diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testSceneIndexDirtying.cpp b/test/lib/mayaUsd/render/mayaToHydra/cpp/testSceneIndexDirtying.cpp new file mode 100644 index 000000000..a2963a3c2 --- /dev/null +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testSceneIndexDirtying.cpp @@ -0,0 +1,46 @@ +// Copyright 2024 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "testUtils.h" + +#include +#include + +#include + +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +TEST(SceneIndexDirtying, testDirtyingNew) +{ + // Setup notifications accumulator for the first terminal scene index + const SceneIndicesVector& sceneIndices = GetTerminalSceneIndices(); + ASSERT_GT(sceneIndices.size(), 0u); + SceneIndexNotificationsAccumulator notifsAccumulator(sceneIndices.front()); + + // Set wireframeOnShaded mode + MString wireframeOnCmd = "select -cl; modelEditor -e -wireframeOnShaded 1 modelPanel4; refresh;"; + + MGlobal::executeCommand(wireframeOnCmd); + + const auto& dirtiedPrimEntries = notifsAccumulator.GetDirtiedPrimEntries(); + const auto& addedPrimEntries = notifsAccumulator.GetAddedPrimEntries(); + const auto& removedPrimEntries = notifsAccumulator.GetRemovedPrimEntries(); + + EXPECT_TRUE(dirtiedPrimEntries.size()); // Expect non-zero dirtied prims. + EXPECT_FALSE(addedPrimEntries.size()); // Changing Hydra reprs(via reprSelectorSceneIndex) + EXPECT_FALSE(removedPrimEntries.size()); // should not cause prim Addition or removal. +} diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testSceneIndexDirtying.py b/test/lib/mayaUsd/render/mayaToHydra/cpp/testSceneIndexDirtying.py new file mode 100644 index 000000000..84b874947 --- /dev/null +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testSceneIndexDirtying.py @@ -0,0 +1,35 @@ +# Copyright 2024 Autodesk +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import maya.cmds as cmds +import fixturesUtils +import mtohUtils +from testUtils import PluginLoaded + +class TestSceneIndexDirtying(mtohUtils.MayaHydraBaseTestCase): + # MayaHydraBaseTestCase.setUpClass requirement. + _file = __file__ + + def setupScene(self): + self.setHdStormRenderer() + cmds.polySphere() + cmds.refresh() + + def test_Dirtying(self): + self.setupScene() + with PluginLoaded('mayaHydraCppTests'): + cmds.mayaHydraCppTest(f="SceneIndexDirtying.testDirtyingNew") + +if __name__ == '__main__': + fixturesUtils.runTests(globals())