Commit 56f320fb authored by David Thompson's avatar David Thompson
Browse files

Progres on face creation (add "force create face").

This adds a new operator that adds a face given a sequence
of points defining the outer loop and optionally additional
points specifying inner loops.
The edge(s) and face are both created by the operator without sanity checks.
The tessellation is obtained by constructing a boost polygon and querying
it for trapezoids which are turned into a triangle fan (for various reasons).
parent 1c39c0be
......@@ -7,6 +7,7 @@ set(polygonSrcs
operators/CreateVertices.cxx
operators/CreateEdge.cxx
operators/CreateFaces.cxx
operators/ForceCreateFace.cxx
operators/SplitEdge.cxx
)
......@@ -18,6 +19,7 @@ set(polygonHeaders
operators/CreateVertices.h
operators/CreateEdge.h
operators/CreateFaces.h
operators/ForceCreateFace.h
operators/SplitEdge.h
)
......@@ -26,6 +28,7 @@ smtk_operator_xml("${CMAKE_CURRENT_SOURCE_DIR}/operators/CreateModel.sbt" polygo
smtk_operator_xml("${CMAKE_CURRENT_SOURCE_DIR}/operators/CreateVertices.sbt" polygonOperatorXML)
smtk_operator_xml("${CMAKE_CURRENT_SOURCE_DIR}/operators/CreateEdge.sbt" polygonOperatorXML)
smtk_operator_xml("${CMAKE_CURRENT_SOURCE_DIR}/operators/CreateFaces.sbt" polygonOperatorXML)
smtk_operator_xml("${CMAKE_CURRENT_SOURCE_DIR}/operators/ForceCreateFace.sbt" polygonOperatorXML)
smtk_operator_xml("${CMAKE_CURRENT_SOURCE_DIR}/operators/SplitEdge.sbt" polygonOperatorXML)
add_library(smtkPolygonSession ${polygonSrcs})
......
#ifndef __smtk_bridge_polygon_internal_Util_h
#define __smtk_bridge_polygon_internal_Util_h
#include "smtk/bridge/polygon/internal/Config.h"
namespace smtk {
namespace bridge {
namespace polygon {
inline internal::HighPrecisionCoord dot2d(const internal::Coord oa[2], const internal::Coord oo[2])
{
internal::HighPrecisionCoord result;
result =
static_cast<internal::HighPrecisionCoord>(oa[0]) * oo[0] +
static_cast<internal::HighPrecisionCoord>(oa[1]) * oo[1];
return result;
}
inline internal::HighPrecisionCoord cross2d(const internal::Coord oa[2], const internal::Coord oo[2])
{
internal::HighPrecisionCoord result;
result =
static_cast<internal::HighPrecisionCoord>(oa[0]) * oo[1] -
static_cast<internal::HighPrecisionCoord>(oa[1]) * oo[0];
return result;
}
} // namespace polygon
} // namespace bridge
} // namespace smtk
#endif // __smtk_bridge_polygon_internal_Util_h
......@@ -13,6 +13,7 @@
#include "smtk/bridge/polygon/internal/Config.h"
#include "smtk/bridge/polygon/internal/Model.h"
#include "smtk/bridge/polygon/internal/Edge.h"
#include "smtk/bridge/polygon/internal/Util.h"
#include "smtk/common/UnionFind.h"
......@@ -471,24 +472,6 @@ struct Region
/// A map to hold each region's definition indexed by its UF region ID.
typedef std::map<RegionIdSet::value_type,smtk::shared_ptr<Region> > RegionDefinitions;
internal::HighPrecisionCoord dot2d(const internal::Coord oa[2], const internal::Coord oo[2])
{
internal::HighPrecisionCoord result;
result =
static_cast<internal::HighPrecisionCoord>(oa[0]) * oo[0] +
static_cast<internal::HighPrecisionCoord>(oa[1]) * oo[1];
return result;
}
internal::HighPrecisionCoord cross2d(const internal::Coord oa[2], const internal::Coord oo[2])
{
internal::HighPrecisionCoord result;
result =
static_cast<internal::HighPrecisionCoord>(oa[0]) * oo[1] -
static_cast<internal::HighPrecisionCoord>(oa[1]) * oo[0];
return result;
}
typedef std::vector<std::pair<smtk::model::Edge,bool> > OrientedEdges;
/**\brief Represent the neighborhood of a sweepline point, x.
......@@ -1031,7 +1014,7 @@ struct Neighborhood
orientation = nextRegion == lr ? false : true;
std::cout << (orientation ? "+" : "-");
}
while (fragId != fragStart || orientation != orientStart);
while ((fragId != fragStart || orientation != orientStart) && !frag->marked(orientation)); // stop infinity on buggy edges
std::cout << "\n";
}
......@@ -1345,13 +1328,6 @@ smtk::model::OperatorResult CreateFaces::operateInternal()
{
neighborhood.processQueue();
}
if (fragments.size() > 14)
{
EdgeFragment& f13(fragments[13]);
EdgeFragment& f14(fragments[14]);
std::cout << "f13 lo " << &(f13.lo()) << " hi " << &(f13.hi()) << " (" << f13.lo().x() << " " << f13.lo().y() << " -- " << f13.hi().x() << " " << f13.hi().y() << ")\n";
std::cout << "f14 lo " << &(f14.lo()) << " hi " << &(f14.hi()) << " (" << f14.lo().x() << " " << f14.lo().y() << " -- " << f14.hi().x() << " " << f14.hi().y() << ")\n";
}
}
// Now we have a single loop for each region, obtainable by
......
//=============================================================================
// 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/bridge/polygon/operators/ForceCreateFace.h"
#include "smtk/bridge/polygon/Session.h"
#include "smtk/bridge/polygon/internal/Config.h"
#include "smtk/bridge/polygon/internal/Model.h"
#include "smtk/bridge/polygon/internal/Edge.h"
#include "smtk/bridge/polygon/internal/Util.h"
#include "smtk/common/UnionFind.h"
#include "smtk/io/Logger.h"
#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/DoubleItem.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/attribute/ModelEntityItem.h"
#include "smtk/attribute/StringItem.h"
#include "smtk/model/Tessellation.h"
#include "smtk/model/Edge.h"
#include "smtk/model/EdgeUse.h"
#include "smtk/model/Face.h"
#include "smtk/model/FaceUse.h"
#include "smtk/model/Loop.h"
#include "smtk/model/Manager.txx"
#include "smtk/bridge/polygon/ForceCreateFace_xml.h"
#include "boost/polygon/polygon.hpp"
#include <deque>
#include <map>
#include <set>
#include <vector>
namespace poly = boost::polygon;
using namespace boost::polygon::operators;
namespace smtk {
namespace bridge {
namespace polygon {
smtk::model::OperatorResult ForceCreateFace::operateInternal()
{
smtk::attribute::DoubleItem::Ptr pointsItem = this->findDouble("points");
smtk::attribute::IntItem::Ptr coordinatesItem = this->findInt("coordinates");
smtk::attribute::IntItem::Ptr offsetsItem = this->findInt("offsets");
int numCoordsPerPt = coordinatesItem->value(0);
smtk::attribute::ModelEntityItem::Ptr modelItem = this->specification()->associations();
smtk::model::Model model;
bool ok = true;
Session* sess = this->polygonSession();
smtk::model::ManagerPtr mgr;
if (!sess || !(mgr = sess->manager()))
{
// error logging requires mgr...
return this->createResult(smtk::model::OPERATION_FAILED);
}
model = modelItem->value(0).as<smtk::model::Model>();
internal::pmodel::Ptr pmodel = this->findStorage<internal::pmodel>(model.entity());
if (!pmodel)
{
smtkErrorMacro(this->log(), "The associated model is not a polygon-session model.");
return this->createResult(smtk::model::OPERATION_FAILED);
}
if (pointsItem->numberOfValues() % numCoordsPerPt > 0)
{
smtkErrorMacro(this->log(),
"Number of point-coordinates (" << pointsItem->numberOfValues() << ") " <<
"not a multiple of the number of coordinates per pt (" << numCoordsPerPt << ")");
return this->createResult(smtk::model::OPERATION_FAILED);
}
// I. Map user point coordinates into model points.
std::vector<double>::const_iterator cit; // point-coordinate iterator
std::vector<internal::Point> polypts;
polypts.reserve(pointsItem->numberOfValues() / numCoordsPerPt);
for (cit = pointsItem->begin(); cit != pointsItem->end(); cit += numCoordsPerPt)
{
internal::Point pt = pmodel->projectPoint(cit, cit + numCoordsPerPt);
polypts.push_back(pt);
}
// II. Invoke boost's create_polygon to get actual edges to create.
int oidx = 0;
std::size_t numOffsets = offsetsItem->numberOfValues();
std::size_t numPts = polypts.size();
int offset = offsetsItem->value(oidx);
if (offset == 0)
{
++oidx;
offset = (oidx == numOffsets) ?
pointsItem->numberOfValues() / numCoordsPerPt :
offsetsItem->value(oidx);
}
if (offset <= 3 || offset >= static_cast<int>(polypts.size()))
{
smtkErrorMacro(this->log(), "Invalid offset (" << offset << ") or too few points (" << polypts.size() << ").");
return this->createResult(smtk::model::OPERATION_FAILED);
}
poly::polygon_with_holes_data<internal::Coord> pface;
pface.set(polypts.begin(), polypts.begin() + offset);
if (offset < static_cast<int>(numPts))
{
std::vector<poly::polygon_data<internal::Coord> > holes;
std::vector<int> holeOffsets(offsetsItem->begin() + oidx, offsetsItem->end());
holeOffsets.push_back(pointsItem->numberOfValues() / numCoordsPerPt);
std::vector<int>::const_iterator oit; // point-coordinate iterator
for (oit = holeOffsets.begin(); oit != holeOffsets.end(); ++oit)
{
if (*oit == offset)
continue;
poly::polygon_data<internal::Coord> loop;
loop.set(polypts.begin() + offset, polypts.begin() + *oit);
holes.push_back(loop);
offset = *oit;
}
pface.set_holes(holes.begin(), holes.end());
}
// III. Create edges
smtk::model::Operator::Ptr ceo = sess->op("create edge");
if (!ceo)
{
smtkErrorMacro(this->log(), "Could not create model edge operator.");
return this->createResult(smtk::model::OPERATION_FAILED);
}
ceo->associateEntity(model);
ceo->findInt("construction method")->setDiscreteIndex(0); // construct from points, not vertices
ceo->findInt("coordinates")->setValue(0, numCoordsPerPt);
ceo->findInt("offsets")->setValues(offsetsItem->begin(), offsetsItem->end());
ceo->findDouble("points")->setValues(pointsItem->begin(), pointsItem->end());
smtk::model::OperatorResult edgeResult = ceo->operate();
if (edgeResult->findInt("outcome")->value(0) != smtk::model::OPERATION_SUCCEEDED)
{
smtkErrorMacro(this->log(), "Could not create model edge(s) of face.");
return this->createResult(smtk::model::OPERATION_FAILED);
}
// TODO
// IV. Transcribe face (uses, loops, face) to smtk
//
// Make up some UUIDs for the new entities.
std::vector<std::pair<smtk::model::Edge, bool> > orientedLoopEdges;
orientedLoopEdges.push_back(
std::make_pair(edgeResult->findModelEntity("created")->value(0).as<smtk::model::Edge>(), true));
smtk::common::UUID modelFaceId = mgr->unusedUUID();
smtk::common::UUID modelFaceUseId = mgr->unusedUUID();
smtk::common::UUID modelLoopId = mgr->unusedUUID();
if (!mgr->insertModelFaceWithOrientedOuterLoop(modelFaceId, modelFaceUseId, modelLoopId, orientedLoopEdges))
{
smtkErrorMacro(this->log(), "Could not create SMTK outer loop of face.");
return this->createResult(smtk::model::OPERATION_FAILED);
}
smtk::model::Face modelFace(mgr, modelFaceId);
model.addCell(modelFace);
modelFace.assignDefaultName();
// V. Add tessellation
poly::polygon_set_data<internal::Coord> polys;
std::vector<poly::polygon_data<internal::Coord> > tess;
polys += pface;
get_trapezoids(tess, polys);
std::vector<poly::polygon_data<internal::Coord> >::const_iterator pit;
smtk::model::Tessellation blank;
smtk::model::UUIDsToTessellations::iterator smtkTess =
mgr->setTessellation(modelFaceId, blank);
double smtkPt[3];
for (pit = tess.begin(); pit != tess.end(); ++pit)
{
//std::cout << "Fan\n";
poly::polygon_data<internal::Coord>::iterator_type pcit;
pcit = poly::begin_points(*pit);
std::vector<int> triConn;
triConn.resize(4);
triConn[0] = smtk::model::TESS_TRIANGLE;
pmodel->liftPoint(*pcit, &smtkPt[0]);
triConn[1] = smtkTess->second.addCoords(&smtkPt[0]);
//std::cout << " " << triConn[1] << " " << smtkPt[0] << " " << smtkPt[1] << " " << smtkPt[2] << "\n";
++pcit;
pmodel->liftPoint(*pcit, &smtkPt[0]);
triConn[3] = smtkTess->second.addCoords(&smtkPt[0]);
++pcit;
//std::cout << " " << triConn[3] << " " << smtkPt[0] << " " << smtkPt[1] << " " << smtkPt[2] << "\n";
for (; pcit != poly::end_points(*pit); ++pcit)
{
triConn[2] = triConn[3];
pmodel->liftPoint(*pcit, &smtkPt[0]);
triConn[3] = smtkTess->second.addCoords(&smtkPt[0]);
//std::cout << " " << triConn[3] << " " << smtkPt[0] << " " << smtkPt[1] << " " << smtkPt[2] << "\n";
smtkTess->second.insertNextCell(triConn);
}
//std::cout << "\n";
}
smtk::model::OperatorResult result;
if (ok)
{
result = this->createResult(smtk::model::OPERATION_SUCCEEDED);
this->addEntityToResult(result, modelFace, CREATED);
}
if (!result)
{
result = this->createResult(smtk::model::OPERATION_FAILED);
}
return result;
}
} // namespace polygon
} //namespace bridge
} // namespace smtk
smtkImplementsModelOperator(
SMTKPOLYGONSESSION_EXPORT,
smtk::bridge::polygon::ForceCreateFace,
polygon_force_create_face,
"force create face",
ForceCreateFace_xml,
smtk::bridge::polygon::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_session_polygon_ForceCreateFace_h
#define __smtk_session_polygon_ForceCreateFace_h
#include "smtk/bridge/polygon/Operator.h"
#include "smtk/common/UnionFind.h"
#include <map>
#include <set>
#include <vector>
namespace smtk {
namespace bridge {
namespace polygon {
/**\brief Create a face given a set of point coordinates or edges (but not both).
*
*/
class SMTKPOLYGONSESSION_EXPORT ForceCreateFace : public Operator
{
public:
smtkTypeMacro(ForceCreateFace);
smtkCreateMacro(ForceCreateFace);
smtkSharedFromThisMacro(Operator);
smtkDeclareModelOperator();
protected:
virtual smtk::model::OperatorResult operateInternal();
};
} // namespace polygon
} //namespace bridge
} // namespace smtk
#endif // __smtk_session_polygon_ForceCreateFace_h
<?xml version="1.0" encoding="utf-8" ?>
<!-- Description of the polygon "ForceCreateFace" operator -->
<SMTK_AttributeSystem Version="2">
<Definitions>
<!-- Operator -->
<AttDef Type="force create face" BaseType="operator">
<BriefDescription>Create a model face without sanity checks.</BriefDescription>
<DetailedDescription>
Create a model face from a sequence of points holding an outer loop and
zero or more inner loops. Each loop will become a single edge.
</DetailedDescription>
<AssociationsDef Name="model" NumberOfRequiredValues="1" Extensible="yes">
<MembershipMask>model</MembershipMask>
<BriefDescription>The model to which faces should be added.</BriefDescription>
<DetailedDescription>
The model to which faces should be added; this must be a polygon-session model.
</DetailedDescription>
</AssociationsDef>
<ItemDefinitions>
<Double Name="points" NumberOfRequiredValues="6" Extensible="yes">
<BriefDescription>The (x,y,z) coordinates of the face points.</BriefDescription>
<DetailedDescription>
The world coordinates of 3 or more points forming 1 or more faces.
Multiple faces may be created by specifying "offsets".
If the default offsets are used, all points are assumed to be
on the outer loop of a single face.
If only 2 coordinates are specified per point, the third is assumed to be 0.
Be sure to set the value of the coordinates item as required.
</DetailedDescription>
</Double>
<Int Name="coordinates" NumberOfRequiredValues="1">
<BriefDescription>The number of coordinates per vertex.</BriefDescription>
<DetailedDescription>
Specify whether 2 or 3 coordinates are provided per vertex.
When set to 2, the third coordinate is assumed to be 0 for all points.
</DetailedDescription>
<DefaultValue>2</DefaultValue>
<RangeInfo>
<Min Inclusive="true">2</Min>
<Max Inclusive="true">3</Max>
</RangeInfo>
</Int>
<Int Name="offsets" NumberOfRequiredValues="1" Extensible="true">
<DefaultValue>0</DefaultValue>
<BriefDescription>Offsets into the list of points where the face or its holes start.</BriefDescription>
<DetailedDescription>
Offsets into the list of points where the face or its holes start.
The first number specifies an offset into the list of points
for the first face's outer loop. It should be 0.
This is followed by an integer offset for each hole.
</DetailedDescription>
</Int>
</ItemDefinitions>
</AttDef>
<!-- Result -->
<AttDef Type="result(force create face)" BaseType="result">
<ItemDefinitions>
<!-- The faces created are reported in the base result's "created" item. -->
</ItemDefinitions>
</AttDef>
</Definitions>
</SMTK_AttributeSystem>
......@@ -141,16 +141,34 @@ class TestPolygonCreation(smtk.testing.TestCase):
arf = SplitEdge(elist, [2, 4])
innerLoopVerts = [ \
[1.25,3.35], [1.65,3.75], [1.25,3.75], [1.25,3.35], \
[1.35,3.25], [1.75,3.25], [1.75,3.65], [1.35,3.25], \
[0.25,4.25], [0.75,4.75], \
[0.25,4.25], [0.75,4.25], [0.75,4.75], \
[0.25,4.25], [0.25,4.75], [0.75,4.75], \
]
innerLoopOffsets = [0, 4];
[1.25,3.35], [1.65,3.75], [1.25,3.75], [1.25,3.35], \
[1.35,3.25], [1.75,3.25], [1.75,3.65], [1.35,3.25], \
[0.25,4.25], [0.75,4.75], \
[0.25,4.25], [0.75,4.25], [0.75,4.75], \
[0.25,4.25], [0.25,4.75], [0.75,4.75], \
]
#innerLoopOffsets = [0, 4]
innerLoopOffsets = [0, 4, 8, 10, 13];
elist = CreateEdge(innerLoopVerts, offsets=innerLoopOffsets, model=mod)
# Square pierced by edges
weirdVerts = [ \
[4.0,0.5], [4.0,0.0], [5.0,0.0], \
[4.0,0.5], [4.0,1.0], [5.0,1.0], \
[5.0,0.0], [5.0,1.0], \
[3.5,0.5], [4.0,0.5], \
[4.0,0.5], [4.5,0.5], \
\
[4.0,1.5], [4.0,2.5], [4.5,2.5], \
[5.0,1.5], [5.0,2.5], [4.5,2.5], \
[4.0,1.5], [5.0,1.5], \
[4.0,1.5], [5.0,1.5], \
[4.5,2.0], [4.5,2.5], \
[4.5,2.0], [4.5,3.0], \
]
weirdOffsets = [0, 3, 6, 8, 10, 12, 15, 18, 20, 22, 24]
elist = CreateEdge(weirdVerts, offsets=weirdOffsets, model=mod)
smtk.io.ExportJSON.fromModelManagerToFile(self.mgr, '/tmp/poly.json')
flist = CreateFaces(mod)
......
#!/usr/bin/python
import sys
#=============================================================================
#
# 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 smtk
from smtk.simple import *
import smtk.testing
class TestPolygonCreation(smtk.testing.TestCase):
def setUp(self):
self.writeJSON = False
self.mgr = smtk.model.Manager.create()
sess = self.mgr.createSession('polygon')
brg = sess.session()
print sess
print brg
sess.assignDefaultName()
SetActiveSession(sess)
print '\n\n%s: type "%s" %s %s' % \
(sess.name(), brg.name(), sess.flagSummary(0), brg.sessionId())
print ' Site: %s' % (sess.site() or 'local')
# We could evaluate the session tag as JSON, but most of
# the information is available through methods above that
# we needed to test:
sessiontag = sess.tag()
print '\n'
#opnames = sess.operatorNames()
#print opnames
def checkModel(self, mod, origin, x_axis, y_axis, normal, feature_size, model_scale):
self.assertEqual(mod.floatProperty('origin'), origin, 'Bad origin')
[self.assertAlmostEqual(mod.floatProperty('x axis')[i], x_axis[i], 'Bad x axis') for i in range(3)]
self.assertEqual(mod.floatProperty('y axis'), y_axis, 'Bad y axis')
self.assertEqual(mod.floatProperty('normal'), normal, 'Bad normal')
self.assertEqual(mod.floatProperty('feature size'), [feature_size,], 'Bad feature size')
self.assertEqual(mod.integerProperty('model scale'), [int(model_scale / feature_size),], 'Bad model scale')
#print smtk.io.ExportJSON.fromModelManager(self.mgr, smtk.io.JSON_DEFAULT)
# Print a summary of the model:
print 'Model ', mod.entity()
print ' x axis ', (' {:.3g}'*3).format(*mod.floatProperty('x axis'))
print ' y axis ', (' {:.3g}'*3).format(*mod.floatProperty('y axis'))
print ' normal ', (' {:.3g}'*3).format(*mod.floatProperty('normal'))
print ' feature size {:14.3g}'.format(mod.floatProperty('feature size')[0])
print ' model scale {:14d}'.format(mod.integerProperty('model scale')[0])
# Square pierced by edges
weirdVerts = [ \
0.0,0.0, 5.0,0.0, 5.0,5.0, 0.0,5.0, \
0.0,0.0, \
1.0,1.0, 2.0,1.0, 2.0,4.0, 1.0,4.0, \
1.0,1.0, \
3.0,2.0, 4.0,2.0, 4.0,3.0, 3.0,3.0, \
3.0,2.0, \
]
weirdOffsets = [0, 5, 10]
fop = GetActiveSession().op('force create face')
fop.associateEntity(mod)
SetVectorValue(fop.findAsInt('offsets'), weirdOffsets)
SetVectorValue(fop.findAsDouble('points'), weirdVerts)
fop.findAsInt('coordinates').setValue