Commit 47770bb8 authored by David Thompson's avatar David Thompson Committed by Kitware Robot

Merge topic 'filter-by-property'

d19cb005 Filter model entities by property type/name/value.
Acked-by: Kitware Robot's avatarKitware Robot <kwrobot@kitware.com>
Acked-by: Bob Obara's avatarBob Obara <bob.obara@kitware.com>
Merge-request: !1513
parents 9127127b d19cb005
Pipeline #134031 running with stage
......@@ -32,6 +32,7 @@ else()
find_package(Boost @SMTK_MINIMUM_BOOST_VERSION@
COMPONENTS @required_boost_components@ REQUIRED)
find_package(nlohmann_json REQUIRED)
find_package(pegtl REQUIRED)
find_package(MOAB REQUIRED)
set(SMTK_ENABLE_QT_SUPPORT @SMTK_ENABLE_QT_SUPPORT@)
......
......@@ -316,6 +316,11 @@ endif ()
################################################################################
find_package(nlohmann_json REQUIRED)
################################################################################
# PEGTL Related Settings
################################################################################
find_package(pegtl REQUIRED)
################################################################################
# Moab Related Settings
################################################################################
......
## Model system changes
### Resource and Entity API
+ The model resource's `queryOperation()` method is now implemented in
Entity.cxx and includes the ability to filter entities by their type
bits (previously available) and their property values (new functionality).
### Operations
+ The "assign colors" operation now provides a way to set opacity independently
of or in tandem with colors. The user interface takes advantage of this to
provide an opacity slider. Since all model-entity colors have been stored as
a 4-component RGBA vector, the model representation now properly sets block
opacities.
......@@ -83,3 +83,99 @@ These classes are organized like so:
Each relationship shown in the figure above has a corresponding
method in the EntityRef subclasses for accessing the related entities.
Filtering and Searching
=======================
As with all classes that inherit :smtk:`smtk::resource::Resource`, it is possible
to ask the resource to filter its components using a string that specifies some
search criteria (i.e., a filter).
Model resources accept an extended syntax compared to other resource types
since (1) model entities have so many types as described above
and (2) in addition to these types, users and SMTK workflows often mark up these
model entities with properties (covered in the :ref:`model-properties` section)
to provide high-level conceptual information that is useful in preparing simulations.
For example, a geometric model of a motor will have many model faces that might
each be marked with properties to indicate which are bearing surfaces, which are
fastener or alignment surfaces, which surfaces will be in contact with coolant
or fuel, etc.
In order to allow user interface components to only show relevant model entities,
the model resource's :smtk:`queryOperation <smtk::model::Resource::queryOperation>`
method accepts strings in the following format:
type-specifier ``[`` property-type [ ``{`` property-name [ ``=`` property-value ] ``}`` ]
where
+ ``type-specifier`` is any model-entity type specifier string such as `face`, `group`, `model`.
A full list can be found in ``smtk/model/Entity.cxx``.
+ ``property-type`` is one of the following string literals ``string``, ``floating-point``, ``integer``.
+ ``property-name`` is either a single-quoted name or a slash-quoted regular expression
(i.e., a regular expression surrounded by forward slashes such as ``/(foo|bar)/)``.
+ ``property-value`` is one of the following
+ a single, single-quoted string value to match
(when searching for string properties),
+ a single, slash-quoted regular expression to match
(when searching for string properties by regular expression),
+ a single, unquoted integer or floating point value to match
(when searching for properties of those types), or
+ a tuple (indicated with parentheses) of values, as specified above,
to match. Note that this implies the property must be vector-valued
and the length must match the specified tuple's length in order
for a match to be successful.
Whitespace is allowed anywhere but is treated as significant if it is inside
any quoted string value or regular expression.
Note that single quotes are used because these filter strings
will appear in XML and/or JSON serializations that use double-quotes
to mark the start and end of the query string.
The examples below include the double-quotes around the query as a reminder.
For regular expressions, the c++11 standard library is used to search for matches;
the syntax must be accepted by the std::regex constructor and std::regex_search()
must return true when passed property names or values in order for the
corresponding entity to be included in filtered results.
.. list-table:: Examples of valid query strings.
:widths: 40 80
:header-rows: 1
* - Query string
- Results
* - "``model|3``"
- Any model explicitly marked as 3-dimensional. (This example
has no limiting clause is here to be clear that existing query
strings will continue to be accepted.)
* - "``vertex[string]``"
- Vertices with any string properties at all (but not vertices without string properties).
* - "``any[integer{'counter'}]``"
- Any entity with an integer property named 'counter' (regardless of the value).
* - "``face[string{'pedigree'='zz'}]``"
- Faces with a string-property named pedigree whose value is "zz"
* - "``any[floating-point{/.*/=(0,0,0)}]``"
- An entity of any type with any floating-point property whose value is a 3-entry vector of zeros.
* - "``group[string{'alphabet'=('abc', 'def')}]``"
- Any group with a string property named "alphabet" whose value is a vector of 2 strings: one valued "abc" and the next valued "def".
.. list-table:: Invalid non-examples of query strings that will not work.
:widths: 40 80
:header-rows: 1
* - Query string
- Why This is Invalid
* - "``edge,face[integer{'pedigree'=23}]``"
- Multiple queries are not supported yet.
Also, it is unclear whether the limiting clause applies
to both types or just faces.
For now, use multiple filters to handle combination queries
with different limiting clauses.
Note that if this example had used ``edge|face`` instead of ``edge,face``,
it would have been valid; the filter would have apply to edges or faces.
* - "``any[{'pedigree'}]``"
- You must currently specify the property type.
* - "``any[integer{'lattice'=(0,*,*)'}]``"
- There is no way to search for properties with partially-matched array-valued entries.
* - "``any[integer{'counter'=(*,*,*)'}]``"
- There is no way to search for properties whose value is a given length yet.
......@@ -82,6 +82,7 @@ set(smtkCore_public_link_libraries
cJSON
${moab_libs}
nlohmann_json
taocpp::pegtl
)
set(smtkCore_private_link_libraries
......
......@@ -20,15 +20,11 @@ set(modelSrcs
AttributeAssignments.cxx
AuxiliaryGeometry.cxx
AuxiliaryGeometryExtension.cxx
Session.cxx
SessionRef.cxx
SessionIO.cxx
SessionIOJSON.cxx
CellEntity.cxx
Chain.cxx
DefaultSession.cxx
EntityRef.cxx
EntityRefArrangementOps.cxx
DefaultSession.cxx
Edge.cxx
EdgeUse.cxx
Entity.cxx
......@@ -42,6 +38,10 @@ set(modelSrcs
Model.cxx
PointLocatorExtension.cxx
Registrar.cxx
Session.cxx
SessionRef.cxx
SessionIO.cxx
SessionIOJSON.cxx
Shell.cxx
ShellEntity.cxx
Resource.cxx
......@@ -83,11 +83,13 @@ set(modelHeaders
Events.h
Face.h
FaceUse.h
FilterGrammar.h
FloatData.h
GridInfo.h
Group.h
Instance.h
IntegerData.h
LimitingClause.h
Loop.h
Model.h
PointLocatorExtension.h
......
This diff is collapsed.
......@@ -49,6 +49,7 @@ class SMTKCORE_EXPORT Entity : public smtk::resource::Component
public:
using UUID = smtk::common::UUID;
using QueryFunctor = std::function<bool(const smtk::resource::ConstComponentPtr&)>;
//using ResourcePtr = smtk::resource::ResourcePtr;
smtkTypeMacro(Entity);
......@@ -116,6 +117,8 @@ public:
static BitFlags dimensionToDimensionBits(int dim);
static int dimensionBitsToDimension(BitFlags dimBits);
static QueryFunctor filterStringToQueryFunctor(const std::string& spec);
int arrange(ArrangementKind, const Arrangement& arr, int index = -1);
int unarrange(ArrangementKind, int index, bool removeIfLast = false);
bool clearArrangements();
......
This diff is collapsed.
//=========================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//=========================================================================
#ifndef __smtk_model_LimitingClause_h
#define __smtk_model_LimitingClause_h
#include "smtk/CoreExports.h" // for SMTKCORE_EXPORT macro
#include "smtk/resource/PropertyType.h"
#include <string>
#include <vector>
namespace smtk
{
namespace model
{
/**!\brief Parser state for model-entity filter specifications.
*
* This object is created by smtk::model::Entity::filterStringToQueryFunctor()
* and used by the returned functor to evaluate entities for suitability in
* query results.
*/
struct SMTKCORE_EXPORT LimitingClause
{
LimitingClause()
: m_propType(smtk::resource::PropertyType::INVALID_PROPERTY)
{
}
smtk::resource::PropertyType m_propType;
std::string m_propName;
bool m_propNameIsRegex;
std::vector<std::string> m_propStringValues;
std::vector<bool> m_propStringIsRegex;
std::vector<long> m_propIntValues;
std::vector<double> m_propFloatValues;
};
} // namespace model
} // namespace smtk
#endif // __smtk_model_LimitingClause_h
......@@ -1021,70 +1021,12 @@ smtk::resource::ComponentPtr Resource::find(const smtk::common::UUID& uid) const
return std::dynamic_pointer_cast<smtk::resource::Component>(this->findEntity(uid));
}
namespace
{
/// Given an entity and a mask, determine if the entity is accepted by the mask.
bool IsValueValid(const smtk::resource::ConstComponentPtr& comp, smtk::model::BitFlags mask)
{
auto modelEnt = dynamic_pointer_cast<const smtk::model::Entity>(comp);
if (modelEnt)
{
smtk::model::EntityRef c = modelEnt->referenceAs<smtk::model::EntityRef>();
if (!mask)
{
return false; // Nothing can possibly match.
}
if (mask == smtk::model::ANY_ENTITY)
{
return true; // Fast-track the trivial case.
}
smtk::model::BitFlags itemType = c.entityFlags();
// The m_membershipMask must match the entity type, the dimension, and (if the
// item is a group) group constraint flags separately;
// In other words, we require the entity type, the dimension, and the
// group constraints to be acceptable independently.
if (((mask & smtk::model::ENTITY_MASK) && !(itemType & mask & smtk::model::ENTITY_MASK) &&
(itemType & smtk::model::ENTITY_MASK) != smtk::model::GROUP_ENTITY) ||
((mask & smtk::model::ANY_DIMENSION) && !(itemType & mask & smtk::model::ANY_DIMENSION)) ||
((itemType & smtk::model::GROUP_ENTITY) && (mask & smtk::model::GROUP_CONSTRAINT_MASK) &&
!(itemType & mask & smtk::model::GROUP_CONSTRAINT_MASK)))
return false;
if (itemType != mask && itemType & smtk::model::GROUP_ENTITY &&
// if the mask is only defined as "group", don't have to check further for members
mask != smtk::model::GROUP_ENTITY)
{
// If the the membershipMask is the same as itemType, we don't need to check, else
// if the item is a group: recursively check that its members
// all match the criteria. Also, if the HOMOGENOUS_GROUP bit is set,
// require all entries to have the same entity type flag as the first.
smtk::model::BitFlags typeMask = mask;
bool mustBeHomogenous = (typeMask & smtk::model::HOMOGENOUS_GROUP) ? true : false;
if (!(typeMask & smtk::model::NO_SUBGROUPS) && !(typeMask & smtk::model::GROUP_ENTITY))
{
typeMask |= smtk::model::GROUP_ENTITY; // if groups aren't banned, allow them.
}
if (!c.as<model::Group>().meetsMembershipConstraints(c, typeMask, mustBeHomogenous))
{
return false;
}
}
return true;
}
return false;
}
}
/// Given a query string, return a functor that determines if a component is
/// accepted by the query.
std::function<bool(const ConstComponentPtr&)> Resource::queryOperation(
const std::string& queryString) const
{
smtk::model::BitFlags bitflags = queryString.empty()
? smtk::model::ANY_ENTITY
: smtk::model::Entity::specifierStringToFlag(queryString);
return std::bind(IsValueValid, std::placeholders::_1, bitflags);
return smtk::model::Entity::filterStringToQueryFunctor(queryString);
}
// visit all components in the resource.
......
......@@ -48,10 +48,6 @@ add_executable(unitArrangement unitArrangement.cxx)
target_link_libraries(unitArrangement smtkCore)
add_test(NAME unitArrangement COMMAND unitArrangement)
add_executable(unitEntity unitEntity.cxx)
target_link_libraries(unitEntity smtkCore smtkCoreModelTesting)
add_test(NAME unitEntity COMMAND unitEntity)
add_executable(unitExportMeshOperation unitExportMeshOperation.cxx)
target_compile_definitions(unitExportMeshOperation PRIVATE "SMTK_SCRATCH_DIR=\"${CMAKE_BINARY_DIR}/Testing/Temporary\"")
target_link_libraries(unitExportMeshOperation smtkCore smtkCoreModelTesting
......@@ -69,3 +65,16 @@ if (SMTK_DATA_DIR)
COMMAND $<TARGET_FILE:unitExportMeshOperation>
"${SMTK_DATA_DIR}/model/2d/smtk/test2D.json")
endif()
if (SMTK_ENABLE_POLYGON_SESSION)
list(APPEND unit_tests_which_require_data unitEntity.cxx)
endif ()
set(external_libs ${Boost_LIBRARIES})
smtk_unit_tests(
LABEL "Model"
# SOURCES ${unit_tests}
SOURCES_REQUIRE_DATA ${unit_tests_which_require_data}
LIBRARIES smtkCore smtkPolygonSession smtkCoreModelTesting ${external_libs}
)
......@@ -13,8 +13,17 @@
#include "smtk/model/IntegerData.h"
#include "smtk/model/testing/cxx/helpers.h"
#include "smtk/operation/Registrar.h"
#include "smtk/operation/operators/ReadResource.h"
#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/FileItem.h"
#include "smtk/common/testing/cxx/helpers.h"
#include "smtk/session/polygon/Registrar.h"
#include "smtk/session/polygon/Resource.h"
#include <iostream>
#include <sstream>
......@@ -24,6 +33,13 @@ using namespace smtk::common;
using namespace smtk::model;
using namespace smtk::model::testing;
namespace
{
std::string dataRoot = SMTK_DATA_DIR;
std::string writeRoot = SMTK_SCRATCH_DIR;
std::string filename("/model/2d/smtk/epic-trex-drummer.smtk");
}
static const char* correct = "0x00000101 vertex\n"
"0x00000102 edge\n"
"0x00000104 face\n"
......@@ -420,8 +436,126 @@ int TestEntityIOSpecs()
return 0;
}
int main()
int TestEntityQueryFunctor()
{
std::cout << "\nTesting Entity::filterStringToQueryFunctor()\n\n";
// I. Load in a test model
smtk::resource::Manager::Ptr rsrcMgr = smtk::resource::Manager::create();
{
smtk::session::polygon::Registrar::registerTo(rsrcMgr);
}
smtk::operation::Manager::Ptr operMgr = smtk::operation::Manager::create();
{
smtk::operation::Registrar::registerTo(operMgr);
smtk::session::polygon::Registrar::registerTo(operMgr);
}
// Register the resource manager to the operation manager (newly created
// resources will be automatically registered to the resource manager).
operMgr->registerResourceManager(rsrcMgr);
std::string readFilePath = dataRoot + filename;
auto rdr = operMgr->create<smtk::operation::ReadResource>();
rdr->parameters()->findFile("filename")->setValue(readFilePath);
rdr->operate();
smtk::resource::ResourcePtr rsrc = nullptr;
std::for_each(rsrcMgr->resources().begin(), rsrcMgr->resources().end(),
[&rsrc](const smtk::resource::ResourcePtr& rr) {
if (rr && !rsrc)
{
rsrc = rr;
}
});
smtkTest(!!rsrc, "Unable to load resource \"" + readFilePath + "\"");
// II. Try various filters with and without limiting clauses.
// Note that the whitespace here is purposefully included
// to verify that the parser will accept it.
// clang-format off
Entity::QueryFunctor qf;
qf = Entity::filterStringToQueryFunctor("model|2[ integer { 'counter' = ( 0 , 0 ) } ]");
qf = Entity::filterStringToQueryFunctor("edge [ floating-point { 'pressure' = 101.0e3 } ]");
qf = Entity::filterStringToQueryFunctor("any[ floating-point { 'color' = ( 0, 0, 0, -1 ) } ]");
// Find all groups named "drum" (test exact string-property name+value matches).
qf = Entity::filterStringToQueryFunctor("group [ string { /n.me/ = \t( /dr.m/ ) } ]");
// Find all models with the exact cell_counters integer property (test integer property).
auto q2 = Entity::filterStringToQueryFunctor("model[integer{ 'cell_counters' =( 45, 71 , 25 ,0 , 0, 0) }]");
// Find anything with a string name property regardless of value (test name-only matching).
// Note this also tests regular expressions containing square brackets inside the regex...
auto q3 = Entity::filterStringToQueryFunctor("any[ string { /n.[mM]e/ } ]");
// Again test name-only matching, but for integer properties.
auto q4 = Entity::filterStringToQueryFunctor("loop[integer]");
// Test exact floating-point property name+value matches.
auto q5 = Entity::filterStringToQueryFunctor("face[floating-point{'color'=( 1 , 0.666667\t, 0 , 1)}]");
// Test exact integer property name+value matches with scalar value (not vector tuple).
auto q6 = Entity::filterStringToQueryFunctor("any[integer{'visible'=1}]");
// Test exact integer property name+value matches.
auto q7 = Entity::filterStringToQueryFunctor("any[integer{'visible'}]");
// clang-format on
// III. Evaluate each functor on the model data
int qfCount = 0;
int q2Count = 0;
int q3Count = 0;
int q4Count = 0;
int q5Count = 0;
int q6Count = 0;
int q7Count = 0;
smtk::resource::Component::Visitor visitor = [&](const smtk::resource::ComponentPtr& comp) {
if (qf(comp))
{
++qfCount;
}
if (q2(comp))
{
++q2Count;
}
if (q3(comp))
{
++q3Count;
}
if (q4(comp))
{
++q4Count;
}
if (q5(comp))
{
++q5Count;
}
if (q6(comp))
{
++q6Count;
}
if (q7(comp))
{
++q7Count;
}
};
rsrc->visit(visitor);
std::cout << " " << qfCount << " groups named 'drum'.\n";
std::cout << " " << q2Count << " models with the proper cell_counters int-vector.\n";
std::cout << " " << q3Count << " entities with string names.\n";
std::cout << " " << q4Count << " loops with any integer properties.\n";
std::cout << " " << q5Count << " faces colored orange.\n";
std::cout << " " << q6Count << " visible entities.\n";
std::cout << " " << q7Count << " visible+invisible entities.\n";
smtkTest(qfCount == 1, "Expected to find 1 group.");
smtkTest(q2Count == 1, "Expected to find 1 model.");
smtkTest(q3Count == 165, "Expected to find 165 named entities.");
smtkTest(q4Count == 23, "Expected to find 23 loops with integer properties.");
smtkTest(q5Count == 6, "Expected to find 6 of 16 faces colored orange.");
smtkTest(q6Count == 51, "Expected to find 51 of 67 visible entities.");
smtkTest(q7Count == 67, "Expected to find 67 entities with visibility.");
return 0;
}
int unitEntity(int argc, char* argv[])
{
(void)argc;
(void)argv;
int status = 0;
try
{
......@@ -434,5 +568,7 @@ int main()
status |= TestEntitySummary();
status |= TestEntityQueryFunctor();
return status ? 1 : 0;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment