Commit f0a6f598 authored by T.J. Corona's avatar T.J. Corona Committed by Kitware Robot
Browse files

Merge topic 'delaunay-tessellate-face'

019b4634

 Add tessellate face operator.
Acked-by: Kitware Robot's avatarKitware Robot <kwrobot@kitware.com>
Merge-request: !525
parents d1afc588 019b4634
set(source TriangulateFace.cxx)
set(headers TriangulateFace.h)
set(source
TessellateFace.cxx
TriangulateFace.cxx)
set(headers
TessellateFace.h
TriangulateFace.h)
smtk_operator_xml("${CMAKE_CURRENT_SOURCE_DIR}/TriangulateFace.sbt" delaunayOperatorXML)
smtk_operator_xml("${CMAKE_CURRENT_SOURCE_DIR}/TessellateFace.sbt" delaunayOperatorXML)
add_library(smtkDelaunayExt ${source})
target_link_libraries(smtkDelaunayExt
......
//=============================================================================
//
// 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.
//
//=============================================================================
#include "smtk/extension/delaunay/TessellateFace.h"
#include "smtk/io/ModelToMesh.h"
#include "smtk/mesh/Collection.h"
#include "smtk/mesh/ExtractTessellation.h"
#include "smtk/mesh/Manager.h"
#include "smtk/model/Edge.h"
#include "smtk/model/Face.h"
#include "smtk/model/FaceUse.h"
#include "smtk/model/Loop.h"
#include "Discretization/ConstrainedDelaunayMesh.hh"
#include "Discretization/ExcisePolygon.hh"
#include "Mesh/Mesh.hh"
#include "Shape/Point.hh"
#include "Shape/Polygon.hh"
#include "Shape/PolygonUtilities.hh"
#include <algorithm>
namespace {
std::vector<Delaunay::Shape::Point> pointsInLoop(
const smtk::model::Loop& loop, smtk::mesh::CollectionPtr& collection)
{
std::int64_t connectivityLength= -1;
std::int64_t numberOfCells = -1;
std::int64_t numberOfPoints = -1;
//query for all cells
smtk::mesh::PreAllocatedTessellation::determineAllocationLengths(
loop, collection, connectivityLength, numberOfCells, numberOfPoints);
std::vector<std::int64_t> conn( connectivityLength );
std::vector<float> fpoints(numberOfPoints * 3);
smtk::mesh::PreAllocatedTessellation ftess(&conn[0], &fpoints[0]);
ftess.disableVTKStyleConnectivity(true);
ftess.disableVTKCellTypes(true);
smtk::mesh::extractOrderedTessellation(loop, collection, ftess);
std::vector<Delaunay::Shape::Point> points;
for (std::size_t i=0;i<fpoints.size(); i+=3)
{
points.push_back(Delaunay::Shape::Point(fpoints[i], fpoints[i+1]));
}
// loops sometimes have a redundant point at the end. We need to remove it.
if (points.front() == points.back())
{
points.pop_back();
}
return points;
}
template <typename Point, typename PointContainer>
int IndexOf(Point& point, const PointContainer& points)
{
return static_cast<int>(std::distance(points.begin(),
std::find(points.begin(),
points.end(), point)));
}
void ImportDelaunayMesh(const Delaunay::Mesh::Mesh& mesh,
smtk::model::Face& face)
{
smtk::model::Tessellation* tess = face.resetTessellation();
tess->coords().resize(mesh.GetVertices().size()*3);
int index = 0;
double xyz[3];
for (auto& p : mesh.GetVertices())
{
index = IndexOf(p, mesh.GetVertices());
xyz[0] = p.x;
xyz[1] = p.y;
xyz[2] = 0.;
tess->setPoint(index, xyz);
}
index = 0;
for (auto& t : mesh.GetTriangles())
{
tess->addTriangle(IndexOf(t.AB().A(), mesh.GetVertices()),
IndexOf(t.AB().B(), mesh.GetVertices()),
IndexOf(t.AC().B(), mesh.GetVertices()));
}
auto bounds = Delaunay::Shape::Bounds(mesh.GetPerimeter());
const double bbox[6] = {bounds[0], bounds[1], bounds[2], bounds[3], 0., 0.};
face.setBoundingBox(bbox);
}
}
namespace smtk {
namespace model {
TessellateFace::TessellateFace()
{
}
bool TessellateFace::ableToOperate()
{
smtk::model::EntityRef eRef =
this->specification()->findModelEntity("face")->value();
return
this->Superclass::ableToOperate() &&
eRef.isValid() &&
eRef.isFace() &&
eRef.owningModel().isValid();
}
OperatorResult TessellateFace::operateInternal()
{
smtk::model::Face face =
this->specification()->findModelEntity("face")->
value().as<smtk::model::Face>();
smtk::io::ModelToMesh convert;
convert.setIsMerging(false);
smtk::mesh::CollectionPtr collection = convert(this->session()->meshManager(),
this->session()->manager());
// get the face use for the face
smtk::model::FaceUse fu = face.positiveUse();
// check if we have an exterior loop
smtk::model::Loops exteriorLoops = fu.loops();
if (exteriorLoops.size() == 0)
{
// if we don't have loops we are bailing out!
smtkErrorMacro(this->log(), "No loops associated with this face.");
return this->createResult(OPERATION_FAILED);
}
// the first loop is the exterior loop
smtk::model::Loop exteriorLoop = exteriorLoops[0];
// make a polygon from the points in the loop
std::vector<Delaunay::Shape::Point> points =
pointsInLoop(exteriorLoop, collection);
Delaunay::Shape::Polygon p(points);
// if the orientation is not ccw, flip the orientation
if (Delaunay::Shape::Orientation(p) != 1)
{
p = Delaunay::Shape::Polygon(points.rbegin(), points.rend());
}
// discretize the polygon
Delaunay::Discretization::ConstrainedDelaunayMesh discretize;
Delaunay::Mesh::Mesh mesh;
discretize(p, mesh);
// then we excise each inner loop within the exterior loop
Delaunay::Discretization::ExcisePolygon excise;
for (auto& loop : exteriorLoop.containedLoops())
{
std::vector<Delaunay::Shape::Point> points_sub =
pointsInLoop(loop, collection);
Delaunay::Shape::Polygon p_sub(points_sub);
// if the orientation is not ccw, flip the orientation
if (Delaunay::Shape::Orientation(p_sub) != 1)
{
p_sub = Delaunay::Shape::Polygon(points_sub.rbegin(), points_sub.rend());
}
excise(p_sub, mesh);
}
// remove the original collection
this->session()->meshManager()->removeCollection(collection);
// Use the delaunay mesh to retessellate the face
ImportDelaunayMesh(mesh, face);
OperatorResult result = this->createResult(OPERATION_SUCCEEDED);
this->addEntityToResult(result, face, MODIFIED);
result->findModelEntity("tess_changed")->setValue(face);
return result;
}
} // namespace model
} // namespace smtk
#include "smtk/extension/delaunay/Exports.h"
#include "smtk/extension/delaunay/TessellateFace_xml.h"
smtkImplementsModelOperator(
SMTKDELAUNAYEXT_EXPORT,
smtk::model::TessellateFace,
delaunay_tessellate_face,
"tessellate face",
TessellateFace_xml,
smtk::model::Session);
//=========================================================================
// 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_extension_delaunay_TessellateFace_h
#define __smtk_extension_delaunay_TessellateFace_h
#include "smtk/extension/delaunay/Exports.h"
#include "smtk/model/Operator.h"
namespace smtk {
namespace model {
class Session;
/**\brief Tessellate a model face using Delaunay.
*
* This operation updates the smtk::mesh::Tessellation associated with an
* smtk::mesh::Face using Delaunay.
*/
class SMTKDELAUNAYEXT_EXPORT TessellateFace : public Operator
{
public:
smtkTypeMacro(TessellateFace);
smtkSuperclassMacro(Operator);
smtkCreateMacro(TessellateFace);
smtkSharedFromThisMacro(Operator);
smtkDeclareModelOperator();
virtual bool ableToOperate();
protected:
TessellateFace();
virtual smtk::model::OperatorResult operateInternal();
};
} // namespace model
} // namespace smtk
#endif // __smtk_extension_delaunay_TessellateFace_h
<?xml version="1.0" encoding="utf-8" ?>
<!-- Description of the model "Tessellate Face" Operator -->
<SMTK_AttributeSystem Version="2">
<Definitions>
<!-- Operator -->
<AttDef Type="tessellate face" Label="Face - Tessellate" BaseType="operator">
<BriefDescription>Tessellate a model face.</BriefDescription>
<DetailedDescription>
Tessellate a model face using Delaunay.
This operation updates the smtk::mesh::Tessellation associated with an
smtk::mesh::Face using Delaunay.
</DetailedDescription>
<ItemDefinitions>
<ModelEntity Name="face" NumberOfRequiredValues="1"/>
</ItemDefinitions>
</AttDef>
<!-- Result -->
<AttDef Type="result(tessellate face)" BaseType="result">
<ItemDefinitions>
<ModelEntity Name="tess_changed" NumberOfRequiredValues="1"/>
</ItemDefinitions>
</AttDef>
</Definitions>
</SMTK_AttributeSystem>
......@@ -204,10 +204,18 @@ OperatorResult TriangulateFace::operateInternal()
excise(p_sub, mesh);
}
// remove the original collection and replace it with a new one
// remove the original collection and grab the collection with the same id as
// the model
this->session()->meshManager()->removeCollection(collection);
collection = this->session()->meshManager()->makeCollection();
collection->associateToModel(face.model().entity());
collection = this->session()->meshManager()->collection(
face.model().entity());
if (!collection)
{
// If we can't find this collection, we can create it
collection = this->session()->meshManager()->makeCollection(
face.model().entity());
collection->associateToModel(face.model().entity());
}
// populate the new collection
smtk::mesh::HandleRange cells = ImportDelaunayMesh(mesh, collection);
......
......@@ -18,6 +18,14 @@ namespace smtk {
class Session;
/**\brief Triangulate a model face into a mesh using Delaunay.
*
* This operation creates an smtk::mesh::MeshSet associated with an
* smtk::mesh::Face using Delaunay. The MeshSet resides in the
* smtk::mesh::Collection with the same UUID as the Face's model. If this
* collection does not yet exist during the construction of the mesh, it is
* created and populated with the MeshSet.
*/
class SMTKDELAUNAYEXT_EXPORT TriangulateFace : public Operator
{
public:
......
......@@ -3,7 +3,17 @@
<SMTK_AttributeSystem Version="2">
<Definitions>
<!-- Operator -->
<AttDef Type="triangulate face" BaseType="operator">
<AttDef Type="triangulate face" Label="Face - Triangulate" BaseType="operator">
<BriefDescription>Triangulate a model face.</BriefDescription>
<DetailedDescription>
Triangulate a model face into a mesh using Delaunay.
This operation creates an smtk::mesh::MeshSet associated with an
smtk::mesh::Face using Delaunay. The MeshSet resides in the
smtk::mesh::Collection with the same UUID as the Face's model. If this
collection does not yet exist during the construction of the mesh, it is
created and populated with the MeshSet.
</DetailedDescription>
<ItemDefinitions>
<ModelEntity Name="face" NumberOfRequiredValues="1"/>
</ItemDefinitions>
......
......@@ -19,6 +19,7 @@ set(unit_tests_which_require_data
if(SMTK_ENABLE_POLYGON_SESSION)
list(APPEND unit_tests_which_require_data
UnitTestTriangulateFace.cxx
UnitTestTessellateFace.cxx
)
list(APPEND extra_libs
smtkPolygonSession
......
//=========================================================================
// 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.
//=========================================================================
#include "smtk/extension/delaunay/TessellateFace.h"
#include "smtk/mesh/Collection.h"
#include "smtk/mesh/ExtractTessellation.h"
#include "smtk/mesh/Manager.h"
#include "smtk/model/Edge.h"
#include "smtk/model/EntityIterator.h"
#include "smtk/model/EntityRef.h"
#include "smtk/model/Face.h"
#include "smtk/model/FaceUse.h"
#include "smtk/model/Loop.h"
#include "smtk/io/ExportMesh.h"
#include "smtk/io/ModelToMesh.h"
#include "smtk/mesh/testing/cxx/helpers.h"
#include "smtk/attribute/FileItem.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/bridge/polygon/Operator.h"
#include "smtk/bridge/polygon/Session.h"
#include <fstream>
namespace
{
//SMTK_DATA_DIR and SMTK_SCRATCH_DIR are defined by cmake
std::string data_root = SMTK_DATA_DIR;
std::string scratch_root = SMTK_SCRATCH_DIR;
void removeRefsWithoutTess(smtk::model::EntityRefs& ents)
{
smtk::model::EntityIterator it;
it.traverse(ents.begin(), ents.end(), smtk::model::ITERATE_BARE);
std::vector< smtk::model::EntityRef > withoutTess;
for (it.begin(); !it.isAtEnd(); ++it)
{
if(!it->hasTessellation())
{
withoutTess.push_back(it.current());
}
}
typedef std::vector< smtk::model::EntityRef >::const_iterator c_it;
for(c_it i=withoutTess.begin(); i < withoutTess.end(); ++i)
{
ents.erase(*i);
}
}
}
int UnitTestTessellateFace(int, char** const)
{
// Somehow grab an EntityRef with an associated tessellation
smtk::model::EntityRef eRef;
smtk::model::ManagerPtr modelManager = smtk::model::Manager::create();
smtk::mesh::ManagerPtr meshManager = modelManager->meshes();
smtk::bridge::polygon::Session::Ptr session =
smtk::bridge::polygon::Session::create();
modelManager->registerSession(session);
smtk::model::Model model;
{
std::string file_path(data_root);
file_path += "/mesh/2d/boxWithHole.smtk";
std::ifstream file(file_path.c_str());
if(file.good())
{ //just make sure the file exists
file.close();
smtk::model::Operator::Ptr op = session->op("load smtk model");
op->findFile("filename")->setValue(file_path.c_str());
smtk::model::OperatorResult result = op->operate();
if (result->findInt("outcome")->value() != smtk::model::OPERATION_SUCCEEDED)
{
std::cerr << "Could not load smtk model!\n";
return 1;
}
model = result->findModelEntity("mesh_created")->value();
}
}
{
smtk::model::EntityRefs currentEnts =
modelManager->entitiesMatchingFlagsAs<smtk::model::EntityRefs>(
smtk::model::FACE);
removeRefsWithoutTess(currentEnts);
if (currentEnts.empty())
{
std::cerr<<"No tessellation!"<<std::endl;
return 1;
}
// We only extract the first face
eRef = *currentEnts.begin();
const smtk::model::Face& face = eRef.as<smtk::model::Face>();
if (!face.isValid())
{
std::cerr << "Face is invald\n";
return 1;
}
smtk::model::OperatorPtr tessellateFace = session->op("tessellate face");
if (!tessellateFace)
{
std::cerr << "No tessellate face operator\n";
return 1;
}
tessellateFace->specification()->associateEntity(model);
tessellateFace->specification()->findModelEntity("face")->
setValue(face);
if (!tessellateFace->ableToOperate())
{
std::cerr << "Tessellate face operator cannot operate\n";
return 1;
}
smtk::model::OperatorResult result = tessellateFace->operate();
if (result->findInt("outcome")->value() != smtk::model::OPERATION_SUCCEEDED)
{
std::cerr << "Tessellate face operator failed\n";
return 1;
}
const smtk::model::Face& tessellatedFace =
result->findModelEntity("tess_changed")->value();
if (face != tessellatedFace)
{
std::cerr << "Tessellate face operator did something strange\n";
return 1;
}
const smtk::model::Tessellation* tess = tessellatedFace.hasTessellation();
if (!tess)
{
std::cerr << "Tessellate face operator did not create a tessellation\n";
return 1;
}
if (tess->coords().size() != 8*3 ||
tess->conn().size() != 8*4)
{
std::cerr << "Tessellate face operator did something wrong\n";
return 1;
}
}
return 0;
}
smtkComponentInitMacro(smtk_delaunay_tessellate_face_operator);
......@@ -5,6 +5,7 @@ set(smtkDelaunayExtPythonDataTests)
if (SMTK_ENABLE_POLYGON_SESSION AND SMTK_USE_PYBIND11)
set(smtkDelaunayExtPythonDataTests
${smtkDelaunayExtPythonDataTests}
unitTestTessellateFace
unitTestTriangulateFace
)
endif()
......
#=============================================================================
#
# 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.
#
#=============================================================================
import os
import sys
import unittest
import smtk
if smtk.wrappingProtocol() == 'pybind11':
import smtk.mesh
import smtk.model
import smtk.model.delaunay
import smtk.bridge.polygon
import smtk.testing
from smtk.simple import *
class UnitTessellateFace(smtk.testing.TestCase):
def setUp(self):
self.mgr = smtk.model.Manager.create()
self.meshmgr = self.mgr.meshes()
self.sess = self.mgr.createSession('polygon')
SetActiveSession(self.sess)
self.modelFile = os.path.join(
smtk.testing.DATA_DIR, 'mesh', '2d', 'boxWithHole.smtk')
self.model = LoadSMTKModel(self.modelFile)[0]
def testMeshing2D(self):
if smtk.wrappingProtocol() == 'pybind11':
face = self.mgr.findEntitiesOfType(int(smtk.model.FACE))[0]
else:
face = self.mgr.findEntitiesOfType(smtk.model.FACE, True)[0]
tessellateFace = self.sess.op('tessellate face')
tessellateFace.specification().associateEntity(self.model)
tessellateFace.specification().findModelEntity("face").setValue(face)
result = tessellateFace.operate()
tessellatedFace = face.hasTessellation()
assert(len(tessellatedFace.coords()) == 8*3)
assert(len(tessellatedFace.conn()) == 8*4)