Skip to content

Commit

Permalink
TopoShape: refactor element map
Browse files Browse the repository at this point in the history
Element mapping interface is now moved into TopoShape's parent class
Data::ComplexGeoData.  User can get/set single element mapping with
function ComplexGeoData::get/setElementName() API in both C++ and
Python. Multiple element mapping can be done with get/setElementMap() in
C++, and through attribute ElementMap in Python.

The ComplexGeoData object can optionally be assign a StringHasher
object to encode its element mapped name with StringID. It is exposed
in both C++ as public member variable (and attribute in Python) named
Hasher. ComplexGeoData itself do not persist the string hasher. For
TopoShape, its done by PropertyPartShape. The individual hashes are
persisted inside the property only if the hasher is not assigned
from the owner Document.Hasher.

TopoShape adds a new public member variable called Tag to help auto
generate element mapping.

TopoShape adds two new element mapping aware function, makECompound()
and makEWire(), exposed in both C++ and Python. These function will
auto generate element mappings if both the shape's own Tag and input
shape's Tag are non-zero.

Part::Primitives and Part::Compound now supports element mapping
  • Loading branch information
realthunder committed Mar 5, 2018
1 parent ba4a6e6 commit c38ef04
Show file tree
Hide file tree
Showing 17 changed files with 875 additions and 341 deletions.
139 changes: 138 additions & 1 deletion src/App/ComplexGeoData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,24 @@
#endif

#include <boost/algorithm/string/predicate.hpp>
#include <boost/bimap.hpp>
#include <boost/bimap/multiset_of.hpp>
#include <boost/bimap/unordered_set_of.hpp>
#include <Base/Writer.h>
#include <Base/Reader.h>
#include <Base/Exception.h>
#include "ComplexGeoData.h"

using namespace Data;

namespace Data {
typedef boost::bimap<
boost::bimaps::multiset_of<std::string>,
boost::bimaps::unordered_set_of<std::string>,
boost::bimaps::with_info<App::StringIDRef> > ElementMapBase;
class ElementMap: public ElementMapBase {};
}

TYPESYSTEM_SOURCE_ABSTRACT(Data::Segment , Base::BaseClass);


Expand Down Expand Up @@ -160,7 +174,7 @@ const std::string &ComplexGeoData::elementMapPrefix() {
}

const char *ComplexGeoData::isMappedElement(const char *name) {
if(boost::starts_with(name,elementMapPrefix()))
if(name && boost::starts_with(name,elementMapPrefix()))
return name+elementMapPrefix().size();
return 0;
}
Expand All @@ -180,3 +194,126 @@ std::string ComplexGeoData::newElementName(const char *name) {
return std::string(name,dot);
return name;
}

size_t ComplexGeoData::getElementMapSize() const {
return _ElementMap?_ElementMap->size():0;
}

const char *ComplexGeoData::getElementName(const char *name, bool reverse) const {
if(!name || !_ElementMap)
return name;

if(reverse) {
auto it = _ElementMap->right.find(name);
if(it == _ElementMap->right.end())
return name;
return it->second.c_str();
}
const char *txt = isMappedElement(name);
if(!txt)
return name;
std::string _txt;
// Strip out the trailing '.XXXX' if any
const char *dot = strchr(txt,'.');
if(dot) {
_txt = std::string(txt,dot-txt);
txt = _txt.c_str();
}
auto it = _ElementMap->left.find(txt);
if(it == _ElementMap->left.end())
return name;
return it->second.c_str();
}

std::vector<const char *> ComplexGeoData::getElementMappedNames(const char *element) const {
std::vector<const char *> names;
auto ret = _ElementMap->left.equal_range(element);
for(auto it=ret.first;it!=ret.second;++it)
names.push_back(it->second.c_str());
return names;
}

std::map<std::string, std::string> ComplexGeoData::getElementMap() const {
std::map<std::string, std::string> ret;
if(!_ElementMap) return ret;
for(auto &v : _ElementMap->left)
ret.emplace_hint(ret.cend(),v.first,v.second);
return ret;
}

void ComplexGeoData::setElementMap(const std::map<std::string, std::string> &map) {
if(!_ElementMap)
_ElementMap = std::make_shared<ElementMap>();
else
_ElementMap->clear();
for(auto &v : map)
setElementName(v.first.c_str(),v.second.c_str());
}

const char *ComplexGeoData::setElementName(const char *element, const char *name,
bool overwrite, App::StringIDRef sid)
{
if(!element || !element[0] || !name || !name[0])
throw Base::ValueError("Invalid input");
const char *mapped = isMappedElement(name);
if(mapped)
name = mapped;
if(!_ElementMap) _ElementMap = std::make_shared<ElementMap>();
if(overwrite)
_ElementMap->left.erase(name);
std::string _name;
if(!sid && !Hasher.isNull()) {
sid = Hasher->getID(name);
_name = sid->toString();
name = _name.c_str();
if(overwrite)
_ElementMap->left.erase(name);
}
auto ret = _ElementMap->left.insert(ElementMap::left_map::value_type(name,element,sid));
if(!ret.second && ret.first->second!=element) {
std::ostringstream ss;
ss << "duplicate element mapping '" << name << "->" << element << '/' << ret.first->second;
throw Base::ValueError(ss.str().c_str());
}
return ret.first->first.c_str();
}

void ComplexGeoData::Save(Base::Writer &writer) const {
writer.Stream() << writer.ind() << "<ElementMap";
if(!_ElementMap || _ElementMap->empty())
writer.Stream() << "/>" << std::endl;
else {
writer.Stream() << " count=\"" << _ElementMap->left.size() << "\">" << std::endl;
for(auto &v : _ElementMap->left) {
// We are omitting indentation here to save some space in case of long list of elements
writer.Stream() << "<Element key=\"" <<
v.first <<"\" value=\"" << v.second;
if(v.info)
writer.Stream() << "\" sid=\"" << v.info->value();
writer.Stream() << "\"/>" << std::endl;
}
writer.Stream() << writer.ind() << "</ElementMap>" << std::endl ;
}
}

void ComplexGeoData::Restore(Base::XMLReader &reader) {
resetElementMap();
reader.readElement("ElementMap");
if(reader.hasAttribute("count")) {
size_t count = reader.getAttributeAsUnsigned("count");
for(size_t i=0;i<count;++i) {
reader.readElement("Element");
auto sid = App::StringIDRef();
if(!Hasher.isNull() && reader.hasAttribute("sid"))
sid = Hasher->getID(reader.getAttributeAsInteger("sid"));
setElementName(reader.getAttribute("value"),reader.getAttribute("key"),false,sid);
}
reader.readEndElement("ElementMap");
}
}

unsigned int ComplexGeoData::getMemSize(void) const {
if(_ElementMap)
return _ElementMap->size()*10;
return 0;
}
80 changes: 80 additions & 0 deletions src/App/ComplexGeoData.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@
#ifndef _AppComplexGeoData_h_
#define _AppComplexGeoData_h_

#include <memory>
#include <Base/Placement.h>
#include <Base/Persistence.h>
#include <Base/Handle.h>
#include <Base/Matrix.h>
#include <Base/BoundBox.h>
#include <Base/Rotation.h>
#include "StringHasher.h"

#ifdef __GNUC__
# include <stdint.h>
Expand All @@ -39,6 +41,9 @@
namespace Data
{

class ElementMap;
typedef std::shared_ptr<ElementMap> ElementMapPtr;

/** Segments
* Subelement type of the ComplexGeoData type
* It is used to split an object in further sub-parts.
Expand Down Expand Up @@ -170,8 +175,80 @@ class AppExport ComplexGeoData: public Base::Persistence, public Base::Handled

/// Strip out the trailing element name if there is mapped element name preceeds it.
static std::string newElementName(const char *name);

/** Get element name
*
* @param name: the input name
* @param reverse: if false (default), the function try to map the name
* to the original \c Type + \c Index. If true, then the function map the
* other way round.
*
* @return Returns the found mapping, or else return the original input. The
* return pointer maybe invalidated when new element mapping is added.
*/
const char *getElementName(const char *name, bool reverse=false) const;

/** Get mapped element names
*
* @param element: original element name with \c Type + \c Index
*
* @return a list of mapped names of the give element
*/
std::vector<const char *> getElementMappedNames(const char *element) const;

/** Add a sub-element name mapping.
*
* @param element: the original \c Type + \c Index element name
* @param name: the renamed sub-element name. May or may not start with
* elementMapPrefix().
* @param hasher: in case the raw 'name' is too long. The caller can opt to
* use this hasher to hash the string and shorten it to an integer, which
* is reference counted and persistent.
* @param overwrite: if true, it will overwrite existing names
* @param sid: in case you use a hasher to hash the element name, pass in
* the string id reference using this parameter
*
* @return Returns the stored mapped element name. Note that if hasher is
* provided the stored name will be different from the input name.
*
* An element can have multiple mapped names. However, a name can only be
* mapped to one element
*/
const char *setElementName(const char *element, const char *name,
bool overwrite=false, App::StringIDRef sid=App::StringIDRef());

/** Reset/swap the element map
*
* @param elementMap: optional new element map
*
* @return Returns the existing element map.
*/
ElementMapPtr resetElementMap(ElementMapPtr elementMap=ElementMapPtr()) {
_ElementMap.swap(elementMap);
return elementMap;
}

/// Get the entire element map
std::map<std::string, std::string> getElementMap() const;

/// Set the entire element map
void setElementMap(const std::map<std::string, std::string> &map);

/// Get the current element map size
size_t getElementMapSize() const;
//@}

/** @name Save/restore */
//@{
void Save (Base::Writer &writer) const;
void Restore(Base::XMLReader &reader);
unsigned int getMemSize (void) const;
//@}

public:
/// String hasher for element name shortening
App::StringHasherRef Hasher;

protected:

/// from local to outside
Expand All @@ -187,6 +264,9 @@ class AppExport ComplexGeoData: public Base::Persistence, public Base::Handled
Base::Vector3d tmp = tmpM * vec;
return Base::Vector3f((float)tmp.x,(float)tmp.y,(float)tmp.z);
}

protected:
ElementMapPtr _ElementMap;
};

} //namespace App
Expand Down
43 changes: 43 additions & 0 deletions src/App/ComplexGeoDataPy.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,25 @@
<Documentation>
<UserDocu>Return vertexes and faces from a sub-element</UserDocu>
</Documentation>
</Methode>
<Methode Name="setElementName">
<Documentation>
<UserDocu>
setElementName(element,name,overwrite=False), Set an element name

element : the original element name, e.g. Edge1, Vertex2
name : the new name for the element
overwrite: if true, it will overwrite exiting name

An element can have multiple mapped names. However, a name can only be mapped
to one element
</UserDocu>
</Documentation>
</Methode>
<Methode Name="getElementName" Const="true">
<Documentation>
<UserDocu>getElementName(name,reverse=False) - Return a mapped element name or reverse</UserDocu>
</Documentation>
</Methode>
<Attribute Name="BoundBox" ReadOnly="true">
<Documentation>
Expand All @@ -37,5 +56,29 @@
</Documentation>
<Parameter Name="Matrix" Type="Object" />
</Attribute>
<Attribute Name="Hasher">
<Documentation>
<UserDocu>Get/Set the string hasher of this object</UserDocu>
</Documentation>
<Parameter Name="Hasher" Type="Object" />
</Attribute>
<Attribute Name="ElementMapSize" ReadOnly="true">
<Documentation>
<UserDocu>Get the current element map size</UserDocu>
</Documentation>
<Parameter Name="ElementMapSize" Type="Int" />
</Attribute>
<Attribute Name="ElementMap">
<Documentation>
<UserDocu>Get/Set a dict of element mapping</UserDocu>
</Documentation>
<Parameter Name="ElementMap" Type="Dict" />
</Attribute>
<Attribute Name="ElementTypes" ReadOnly="true">
<Documentation>
<UserDocu>A list of support element types</UserDocu>
</Documentation>
<Parameter Name="ElementTypes" Type="Tuple" />
</Attribute>
</PythonExport>
</GenerateModel>
Loading

0 comments on commit c38ef04

Please sign in to comment.