App: add APIs in ComplexGeoData for element name mapping
These are the fundation for storing and querying the new topological naming of geometry element. To get an overview of this fundation, please check out the following article https://github.com/realthunder/FreeCAD_assembly3/wiki/Topological-Naming There are some changes in topo naming internal storage, which changes from plain string to two dedicated class IndexedName (for indexed geometry element name) and MappedName. These two classes are created to improve topological naming performance in terms of speed, runtime memory, and persistence storage size.
This commit is contained in:
@@ -273,6 +273,7 @@ SET(FreeCADApp_CPP_SRCS
|
||||
AutoTransaction.cpp
|
||||
Branding.cpp
|
||||
ColorModel.cpp
|
||||
MappedElement.cpp
|
||||
ComplexGeoData.cpp
|
||||
ComplexGeoDataPyImp.cpp
|
||||
Enumeration.cpp
|
||||
@@ -289,6 +290,7 @@ SET(FreeCADApp_HPP_SRCS
|
||||
AutoTransaction.h
|
||||
Branding.h
|
||||
ColorModel.h
|
||||
MappedElement.h
|
||||
ComplexGeoData.h
|
||||
Enumeration.h
|
||||
Material.h
|
||||
|
||||
+2095
-17
File diff suppressed because it is too large
Load Diff
+259
-1
@@ -24,9 +24,17 @@
|
||||
#ifndef _AppComplexGeoData_h_
|
||||
#define _AppComplexGeoData_h_
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <cctype>
|
||||
#include <functional>
|
||||
|
||||
#include <QVector>
|
||||
|
||||
#include <Base/Handle.h>
|
||||
#include <Base/Matrix.h>
|
||||
#include <Base/Persistence.h>
|
||||
#include "StringHasher.h"
|
||||
|
||||
#ifdef __GNUC__
|
||||
# include <cstdint>
|
||||
@@ -44,6 +52,16 @@ typedef BoundBox3<double> BoundBox3d;
|
||||
namespace Data
|
||||
{
|
||||
|
||||
class ElementMap;
|
||||
typedef std::shared_ptr<ElementMap> ElementMapPtr;
|
||||
|
||||
typedef QVector<App::StringIDRef> ElementIDRefs;
|
||||
|
||||
class IndexedName;
|
||||
class MappedName;
|
||||
struct MappedElement;
|
||||
struct MappedChildElements;
|
||||
|
||||
/** Segments
|
||||
* Subelement type of the ComplexGeoData type
|
||||
* It is used to split an object in further sub-parts.
|
||||
@@ -165,8 +183,12 @@ public:
|
||||
//@{
|
||||
/// Special prefix to mark the beginning of a mapped sub-element name
|
||||
static const std::string &elementMapPrefix();
|
||||
/// Special postfix to mark the following tag
|
||||
/// Special postfix to mark the following tag encoded as hex number
|
||||
static const std::string &tagPostfix();
|
||||
/// Special postfix to mark the following tag encoded as decimal number
|
||||
static const std::string &decTagPostfix();
|
||||
/// Special postfix to mark the name includes encoding from an external object
|
||||
static const std::string &externalTagPostfix();
|
||||
/// Special postfix to mark the index of an array element
|
||||
static const std::string &indexPostfix();
|
||||
/// Special prefix to mark a missing element
|
||||
@@ -190,18 +212,247 @@ public:
|
||||
|
||||
/// Find the start of an element name in a subname
|
||||
static const char *findElementName(const char *subname);
|
||||
|
||||
/// Check if the given subname contains element name
|
||||
static bool hasElementName(const char *subname) {
|
||||
subname = findElementName(subname);
|
||||
return subname && *subname;
|
||||
}
|
||||
|
||||
/// Return the element name portion of the subname without mapping prefix
|
||||
static inline const char *hasMappedElementName(const char *subname) {
|
||||
return isMappedElement(findElementName(subname));
|
||||
}
|
||||
|
||||
/** Get element indexed name
|
||||
*
|
||||
* @param name: the input name
|
||||
* @param sid: optional output of and App::StringID involved forming this mapped name
|
||||
*
|
||||
* @return Returns an indexed name.
|
||||
*/
|
||||
IndexedName getIndexedName(const MappedName & name,
|
||||
ElementIDRefs *sid = nullptr) const;
|
||||
|
||||
/** Get element mapped name
|
||||
*
|
||||
* @param name: the input name
|
||||
* @param allowUnmapped: If the queried element is not mapped, then return
|
||||
* an empty name if \c allowUnmapped is false, or
|
||||
* else, return the indexed name.
|
||||
* @param sid: optional output of and App::StringID involved forming this mapped name
|
||||
* @return Returns the mapped name.
|
||||
*/
|
||||
MappedName getMappedName(const IndexedName & element,
|
||||
bool allowUnmapped = false,
|
||||
ElementIDRefs *sid = nullptr) const;
|
||||
|
||||
/** Return a pair of indexed name and mapped name
|
||||
*
|
||||
* @param name: the input name.
|
||||
* @param sid: optional output of and App::StringID involved forming this
|
||||
* mapped name
|
||||
* @param copy: if true, copy the name string, or else use it as constant
|
||||
* string, and caller must make sure the memory is not freed.
|
||||
*
|
||||
* @return Returns the MappedElement which contains both the indexed and
|
||||
* mapped name.
|
||||
*
|
||||
* This function guesses whether the input name is an indexed name or
|
||||
* mapped, and perform a lookup and return the names found. If the input
|
||||
* name contains only alphabets and underscore followed by optional digits,
|
||||
* it will be treated as indexed name. Or else, it will be treated as
|
||||
* mapped name.
|
||||
*/
|
||||
MappedElement getElementName(const char * name,
|
||||
ElementIDRefs *sid = nullptr,
|
||||
bool copy = false) const;
|
||||
|
||||
/** Get mapped element with a given prefix */
|
||||
std::vector<MappedElement> getElementNamesWithPrefix(const char *prefix) const;
|
||||
|
||||
/** Get mapped element names
|
||||
*
|
||||
* @param element: original element name with \c Type + \c Index
|
||||
* @param needUnmapped: if true, return the original element name if no
|
||||
* mapping is found
|
||||
*
|
||||
* @return a list of mapped names of the give element along with their
|
||||
* associated string ID references
|
||||
*/
|
||||
std::vector<std::pair<MappedName, ElementIDRefs> >
|
||||
getElementMappedNames(const IndexedName & element, bool needUnmapped=false) const;
|
||||
|
||||
/** Add a sub-element name mapping.
|
||||
*
|
||||
* @param element: the original \c Type + \c Index element name
|
||||
* @param name: the mapped sub-element name. May or may not start with
|
||||
* elementMapPrefix().
|
||||
* @param sid: in case you use a hasher to hash the element name, pass in
|
||||
* the string id reference using this parameter. You can have more than one
|
||||
* string id associated with the same name.
|
||||
* @param overwrite: if true, it will overwrite existing names
|
||||
*
|
||||
* @return Returns the stored mapped element name.
|
||||
*
|
||||
* An element can have multiple mapped names. However, a name can only be
|
||||
* mapped to one element
|
||||
*/
|
||||
MappedName setElementName(const IndexedName & element,
|
||||
const MappedName & name,
|
||||
const ElementIDRefs * sid = nullptr,
|
||||
bool overwrite = false);
|
||||
|
||||
void setMappedChildElements(const std::vector<MappedChildElements> & children);
|
||||
std::vector<MappedChildElements> getMappedChildElements() const;
|
||||
|
||||
/** Convenience method to hash the main element name
|
||||
*
|
||||
* @param name: main element name
|
||||
* @param sid: store any output string ID references
|
||||
* @return the hashed element name;
|
||||
*/
|
||||
MappedName hashElementName(const MappedName & name, ElementIDRefs &sid) const;
|
||||
|
||||
/// Hash the child element map postfixes to shorten element name from hierarchical maps
|
||||
void hashChildMaps();
|
||||
|
||||
/// Check if there is child element map
|
||||
bool hasChildElementMap() const;
|
||||
|
||||
/// Reverse hashElementName()
|
||||
MappedName dehashElementName(const MappedName & name) const;
|
||||
|
||||
/// Append the Tag (if and only if it is non zero) into the element map
|
||||
virtual void reTagElementMap(long tag, App::StringHasherRef hasher, const char *postfix=0) {
|
||||
(void)tag;
|
||||
(void)hasher;
|
||||
(void)postfix;
|
||||
}
|
||||
|
||||
long getElementHistory(const char *name,
|
||||
MappedName *original=0, std::vector<MappedName> *history=0) const;
|
||||
|
||||
long getElementHistory(const MappedName & name,
|
||||
MappedName *original=0, std::vector<MappedName> *history=0) const;
|
||||
|
||||
void encodeElementName(char element_type, MappedName & name, std::ostringstream &ss,
|
||||
ElementIDRefs *sids, const char* postfix=0, long tag=0, bool forceTag=false) const;
|
||||
|
||||
char elementType(const Data::MappedName &) const;
|
||||
char elementType(const Data::IndexedName &) const;
|
||||
char elementType(const char *name) const;
|
||||
|
||||
/** Reset/swap the element map
|
||||
*
|
||||
* @param elementMap: optional new element map
|
||||
*
|
||||
* @return Returns the existing element map.
|
||||
*/
|
||||
virtual ElementMapPtr resetElementMap(ElementMapPtr elementMap=ElementMapPtr()) {
|
||||
_ElementMap.swap(elementMap);
|
||||
return elementMap;
|
||||
}
|
||||
|
||||
/// Get the entire element map
|
||||
std::vector<MappedElement> getElementMap() const;
|
||||
|
||||
/// Set the entire element map
|
||||
void setElementMap(const std::vector<MappedElement> &elements);
|
||||
|
||||
/// Get the current element map size
|
||||
size_t getElementMapSize(bool flush=true) const;
|
||||
|
||||
/// Return the higher level element names of the given element
|
||||
virtual std::vector<IndexedName> getHigherElements(const char *name, bool silent=false) const;
|
||||
|
||||
/// Return the current element map version
|
||||
virtual std::string getElementMapVersion() const;
|
||||
|
||||
/// Return true to signal element map version change
|
||||
virtual bool checkElementMapVersion(const char * ver) const;
|
||||
|
||||
/// Check if the given subname only contains an element name
|
||||
static bool isElementName(const char *subname) {
|
||||
return subname && *subname && findElementName(subname)==subname;
|
||||
}
|
||||
|
||||
/** Extract tag and other information from a encoded element name
|
||||
*
|
||||
* @param name: encoded element name
|
||||
* @param tag: optional pointer to receive the extracted tag
|
||||
* @param len: optional pointer to receive the length field after the tag field.
|
||||
* This gives the length of the previous hashsed element name starting
|
||||
* from the beginning of the give element name.
|
||||
* @param postfix: optional pointer to receive the postfix starting at the found tag field.
|
||||
* @param type: optional pointer to receive the element type character
|
||||
* @param negative: return negative tag as it is. If disabled, then always return positive tag.
|
||||
* Negative tag is sometimes used for element disambiguation.
|
||||
* @param recursive: recursively find the last non-zero tag
|
||||
*
|
||||
* @return Return the end position of the tag field, or return -1 if not found.
|
||||
*/
|
||||
static int findTagInElementName(const MappedName & name,
|
||||
long *tag=0,
|
||||
int *len=0,
|
||||
std::string *postfix=0,
|
||||
char *type=0,
|
||||
bool negative=false,
|
||||
bool recursive=true);
|
||||
|
||||
/** Element trace callback
|
||||
*
|
||||
* The callback has the following call signature
|
||||
* (const std::string &name, size_t offset, long encodedTag, long tag) -> bool
|
||||
*
|
||||
* @param name: the current element name.
|
||||
* @param offset: the offset skipping the encoded element name for the next iteration.
|
||||
* @param encodedTag: the tag encoded inside the current element, which is usually the tag
|
||||
* of the previous step in the shape history.
|
||||
* @param tag: the tag of the current shape element.
|
||||
*
|
||||
* @sa traceElement()
|
||||
*/
|
||||
typedef std::function<bool(const MappedName &, int, long, long)> TraceCallback;
|
||||
|
||||
/** Iterate through the history of the give element name with a given callback
|
||||
*
|
||||
* @param name: the input element name
|
||||
* @param cb: trace callback with call signature.
|
||||
* @sa TraceCallback
|
||||
*/
|
||||
void traceElement(const MappedName &name, TraceCallback cb) const;
|
||||
|
||||
/** Flush an internal buffering for element mapping */
|
||||
virtual void flushElementMap() const;
|
||||
virtual unsigned long getElementMapReserve() const { return 0; }
|
||||
//@}
|
||||
|
||||
/** @name Save/restore */
|
||||
//@{
|
||||
void Save (Base::Writer &writer) const;
|
||||
void Restore(Base::XMLReader &reader);
|
||||
void SaveDocFile(Base::Writer &writer) const;
|
||||
void RestoreDocFile(Base::Reader &reader);
|
||||
unsigned int getMemSize (void) const;
|
||||
void setPersistenceFileName(const char *name) const;
|
||||
virtual void beforeSave() const;
|
||||
bool isRestoreFailed() const { return _restoreFailed; }
|
||||
void resetRestoreFailure() const { _restoreFailed = true; }
|
||||
//@}
|
||||
|
||||
public:
|
||||
/// String hasher for element name shortening
|
||||
mutable App::StringHasherRef Hasher;
|
||||
|
||||
protected:
|
||||
virtual MappedName renameDuplicateElement(int index,
|
||||
const IndexedName & element,
|
||||
const IndexedName & element2,
|
||||
const MappedName & name,
|
||||
ElementIDRefs &sids);
|
||||
|
||||
void restoreStream(std::istream &s, std::size_t count);
|
||||
|
||||
/// from local to outside
|
||||
inline Base::Vector3d transformToOutside(const Base::Vector3f& vec) const
|
||||
@@ -223,9 +474,16 @@ protected:
|
||||
|
||||
public:
|
||||
mutable long Tag;
|
||||
|
||||
protected:
|
||||
ElementMapPtr elementMap(bool flush=true) const;
|
||||
|
||||
protected:
|
||||
mutable std::string _PersistenceName;
|
||||
mutable bool _restoreFailed = false;
|
||||
|
||||
private:
|
||||
ElementMapPtr _ElementMap;
|
||||
};
|
||||
|
||||
} //namespace App
|
||||
|
||||
@@ -64,6 +64,37 @@
|
||||
<UserDocu>Apply a transformation to the underlying geometry</UserDocu>
|
||||
</Documentation>
|
||||
</Methode>
|
||||
<Methode Name="setElementName" Keyword="true">
|
||||
<Documentation>
|
||||
<UserDocu>
|
||||
setElementName(element,name=None,postfix=None,overwrite=False,sid=None), Set an element name
|
||||
|
||||
element : the original element name, e.g. Edge1, Vertex2
|
||||
name : the new name for the element, None to remove the mapping
|
||||
postfix : postfix of the name that will not be hashed
|
||||
overwrite: if true, it will overwrite exiting name
|
||||
sid : to hash the name any way you want, provide your own string id(s) in this parameter
|
||||
|
||||
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,direction=0) - Return a mapped element name or reverse</UserDocu>
|
||||
</Documentation>
|
||||
</Methode>
|
||||
<Methode Name="getElementIndexedName" Const="true">
|
||||
<Documentation>
|
||||
<UserDocu>getElementIndexedName(name) - Return the indexed element name</UserDocu>
|
||||
</Documentation>
|
||||
</Methode>
|
||||
<Methode Name="getElementMappedName" Const="true">
|
||||
<Documentation>
|
||||
<UserDocu>getElementMappedName(name) - Return the mapped element name</UserDocu>
|
||||
</Documentation>
|
||||
</Methode>
|
||||
<Attribute Name="BoundBox" ReadOnly="true">
|
||||
<Documentation>
|
||||
<UserDocu>Get the BoundBox of the object</UserDocu>
|
||||
@@ -88,5 +119,35 @@
|
||||
</Documentation>
|
||||
<Parameter Name="Tag" Type="Int"/>
|
||||
</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="ElementReverseMap" ReadOnly="true">
|
||||
<Documentation>
|
||||
<UserDocu>Get a dict of element reverse mapping</UserDocu>
|
||||
</Documentation>
|
||||
<Parameter Name="ElementReverseMap" Type="Dict" />
|
||||
</Attribute>
|
||||
<Attribute Name="ElementMapVersion" ReadOnly="true">
|
||||
<Documentation>
|
||||
<UserDocu>Element map version</UserDocu>
|
||||
</Documentation>
|
||||
<Parameter Name="ElementMapVersion" Type="String" />
|
||||
</Attribute>
|
||||
</PythonExport>
|
||||
</GenerateModel>
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#endif
|
||||
|
||||
#include "ComplexGeoData.h"
|
||||
#include "MappedElement.h"
|
||||
|
||||
// inclusion of the generated files (generated out of ComplexGeoDataPy.xml)
|
||||
#include <App/ComplexGeoDataPy.h>
|
||||
@@ -36,6 +37,8 @@
|
||||
#include <Base/PlacementPy.h>
|
||||
#include <Base/VectorPy.h>
|
||||
#include <Base/GeometryPyCXX.h>
|
||||
#include <App/StringHasherPy.h>
|
||||
#include <App/StringIDPy.h>
|
||||
|
||||
using namespace Data;
|
||||
using namespace Base;
|
||||
@@ -51,10 +54,9 @@ PyObject* ComplexGeoDataPy::getElementTypes(PyObject *args)
|
||||
if (!PyArg_ParseTuple(args, ""))
|
||||
return nullptr;
|
||||
|
||||
std::vector<const char*> types = getComplexGeoDataPtr()->getElementTypes();
|
||||
Py::List list;
|
||||
for (auto it : types) {
|
||||
list.append(Py::String(it));
|
||||
for (const auto &type : getComplexGeoDataPtr()->getElementTypes()) {
|
||||
list.append(Py::String(type));
|
||||
}
|
||||
return Py::new_reference_to(list);
|
||||
}
|
||||
@@ -302,6 +304,183 @@ PyObject* ComplexGeoDataPy::transformGeometry(PyObject *args)
|
||||
}
|
||||
}
|
||||
|
||||
PyObject* ComplexGeoDataPy::getElementName(PyObject *args)
|
||||
{
|
||||
char* input;
|
||||
int direction = 0;
|
||||
if (!PyArg_ParseTuple(args, "s|i", &input,&direction))
|
||||
return NULL;
|
||||
|
||||
Data::MappedElement res = getComplexGeoDataPtr()->getElementName(input);
|
||||
std::string s;
|
||||
if (direction == 1)
|
||||
return Py::new_reference_to(Py::String(res.name.toString(s)));
|
||||
else if (direction == 0)
|
||||
return Py::new_reference_to(Py::String(res.index.toString(s)));
|
||||
else if (Data::IndexedName(input))
|
||||
return Py::new_reference_to(Py::String(res.name.toString(s)));
|
||||
else
|
||||
return Py::new_reference_to(Py::String(res.index.toString(s)));
|
||||
}
|
||||
|
||||
PyObject* ComplexGeoDataPy::getElementIndexedName(PyObject *args)
|
||||
{
|
||||
char* input;
|
||||
PyObject *returnID = Py_False;
|
||||
if (!PyArg_ParseTuple(args, "s|O", &input,&returnID))
|
||||
return NULL;
|
||||
|
||||
ElementIDRefs ids;
|
||||
Data::MappedElement res = getComplexGeoDataPtr()->getElementName(
|
||||
input, PyObject_IsTrue(returnID)?&ids:nullptr);
|
||||
std::string s;
|
||||
Py::String name(res.index.toString(s));
|
||||
if (!PyObject_IsTrue(returnID))
|
||||
return Py::new_reference_to(name);
|
||||
|
||||
Py::List list;
|
||||
for (auto &id : ids)
|
||||
list.append(Py::Long(id.value()));
|
||||
return Py::new_reference_to(Py::TupleN(name, list));
|
||||
}
|
||||
|
||||
PyObject* ComplexGeoDataPy::getElementMappedName(PyObject *args)
|
||||
{
|
||||
char* input;
|
||||
PyObject *returnID = Py_False;
|
||||
if (!PyArg_ParseTuple(args, "s|O", &input,&returnID))
|
||||
return NULL;
|
||||
|
||||
ElementIDRefs ids;
|
||||
Data::MappedElement res = getComplexGeoDataPtr()->getElementName(
|
||||
input, PyObject_IsTrue(returnID)?&ids:nullptr);
|
||||
std::string s;
|
||||
Py::String name(res.name.toString(s));
|
||||
if (!PyObject_IsTrue(returnID))
|
||||
return Py::new_reference_to(name);
|
||||
|
||||
Py::List list;
|
||||
for (auto &id : ids)
|
||||
list.append(Py::Long(id.value()));
|
||||
return Py::new_reference_to(Py::TupleN(name, list));
|
||||
}
|
||||
|
||||
PyObject *ComplexGeoDataPy::setElementName(PyObject *args, PyObject *kwds) {
|
||||
const char *element;
|
||||
const char *name = 0;
|
||||
const char *postfix = 0;
|
||||
int tag = 0;
|
||||
PyObject *pySid = Py_None;
|
||||
PyObject *overwrite = Py_False;
|
||||
|
||||
static char *kwlist[] = {"element", "name", "postfix", "overwrite", "sid", "tag", NULL};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|sssOOi", kwlist,
|
||||
&element,&name,&postfix,&overwrite,&pySid,&tag))
|
||||
return NULL;
|
||||
ElementIDRefs sids;
|
||||
if(pySid != Py_None) {
|
||||
if(PyObject_TypeCheck(pySid,&App::StringIDPy::Type))
|
||||
sids.push_back(static_cast<App::StringIDPy*>(pySid)->getStringIDPtr());
|
||||
else if(PySequence_Check(pySid)) {
|
||||
Py::Sequence seq(pySid);
|
||||
for(auto it=seq.begin();it!=seq.end();++it) {
|
||||
auto ptr = (*it).ptr();
|
||||
if(PyObject_TypeCheck(ptr,&App::StringIDPy::Type))
|
||||
sids.push_back(static_cast<App::StringIDPy*>(ptr)->getStringIDPtr());
|
||||
else
|
||||
throw Py::TypeError("expect StringID in sid sequence");
|
||||
}
|
||||
} else
|
||||
throw Py::TypeError("expect sid to contain either StringID or sequence of StringID");
|
||||
}
|
||||
PY_TRY {
|
||||
Data::IndexedName index(element, getComplexGeoDataPtr()->getElementTypes());
|
||||
Data::MappedName mapped = Data::MappedName::fromRawData(name);
|
||||
std::ostringstream ss;
|
||||
getComplexGeoDataPtr()->encodeElementName(getComplexGeoDataPtr()->elementType(index),
|
||||
mapped, ss, &sids, postfix, tag);
|
||||
Data::MappedName res = getComplexGeoDataPtr()->setElementName(
|
||||
index, mapped, &sids, PyObject_IsTrue(overwrite));
|
||||
return Py::new_reference_to(Py::String(res.toString(0)));
|
||||
}PY_CATCH
|
||||
}
|
||||
|
||||
Py::Object ComplexGeoDataPy::getHasher() const {
|
||||
auto self = getComplexGeoDataPtr();
|
||||
if(!self->Hasher)
|
||||
return Py::None();
|
||||
return Py::Object(self->Hasher->getPyObject(),true);
|
||||
}
|
||||
|
||||
Py::Dict ComplexGeoDataPy::getElementMap() const {
|
||||
Py::Dict ret;
|
||||
std::string s;
|
||||
for(auto &v : getComplexGeoDataPtr()->getElementMap()) {
|
||||
s.clear();
|
||||
ret.setItem(v.name.toString(0), Py::String(v.index.toString(s)));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ComplexGeoDataPy::setElementMap(Py::Dict dict) {
|
||||
std::vector<Data::MappedElement> map;
|
||||
const auto & types = getComplexGeoDataPtr()->getElementTypes();
|
||||
for(auto it=dict.begin();it!=dict.end();++it) {
|
||||
const auto &value = *it;
|
||||
if(!value.first.isString() || !value.second.isString())
|
||||
throw Py::TypeError("expect only strings in the dict");
|
||||
map.emplace_back(Data::MappedName(value.first.as_string().c_str()),
|
||||
Data::IndexedName(Py::Object(value.second).as_string().c_str(), types));
|
||||
}
|
||||
getComplexGeoDataPtr()->setElementMap(map);
|
||||
}
|
||||
|
||||
Py::Dict ComplexGeoDataPy::getElementReverseMap() const {
|
||||
Py::Dict ret;
|
||||
std::string s;
|
||||
for(auto &v : getComplexGeoDataPtr()->getElementMap()) {
|
||||
s.clear();
|
||||
auto value = ret[Py::String(v.index.toString(s))];
|
||||
Py::Object item(value);
|
||||
if(item.isNone()) {
|
||||
s.clear();
|
||||
value = Py::String(v.name.toString(s));
|
||||
} else if(item.isList()) {
|
||||
Py::List list(item);
|
||||
s.clear();
|
||||
list.append(Py::String(v.name.toString(s)));
|
||||
} else {
|
||||
Py::List list;
|
||||
list.append(item);
|
||||
s.clear();
|
||||
list.append(Py::String(v.name.toString(s)));
|
||||
value = list;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Py::Int ComplexGeoDataPy::getElementMapSize() const {
|
||||
return Py::Int((long)getComplexGeoDataPtr()->getElementMapSize());
|
||||
}
|
||||
|
||||
void ComplexGeoDataPy::setHasher(Py::Object obj) {
|
||||
auto self = getComplexGeoDataPtr();
|
||||
if(obj.isNone()) {
|
||||
if(self->Hasher) {
|
||||
self->Hasher = App::StringHasherRef();
|
||||
self->resetElementMap();
|
||||
}
|
||||
}else if(PyObject_TypeCheck(obj.ptr(),&App::StringHasherPy::Type)) {
|
||||
App::StringHasherRef ref(static_cast<App::StringHasherPy*>(obj.ptr())->getStringHasherPtr());
|
||||
if(self->Hasher != ref) {
|
||||
self->Hasher = ref;
|
||||
self->resetElementMap();
|
||||
}
|
||||
}else
|
||||
throw Py::TypeError("invalid type");
|
||||
}
|
||||
|
||||
Py::Object ComplexGeoDataPy::getBoundBox() const
|
||||
{
|
||||
return Py::BoundingBox(getComplexGeoDataPtr()->getBoundBox());
|
||||
@@ -334,6 +513,11 @@ void ComplexGeoDataPy::setPlacement(Py::Object arg)
|
||||
}
|
||||
}
|
||||
|
||||
Py::String ComplexGeoDataPy::getElementMapVersion() const
|
||||
{
|
||||
return Py::String(getComplexGeoDataPtr()->getElementMapVersion());
|
||||
}
|
||||
|
||||
Py::Int ComplexGeoDataPy::getTag() const
|
||||
{
|
||||
return Py::Int(getComplexGeoDataPtr()->Tag);
|
||||
|
||||
@@ -1270,6 +1270,20 @@ bool DocumentObject::adjustRelativeLinks(
|
||||
return touched;
|
||||
}
|
||||
|
||||
std::string DocumentObject::getElementMapVersion(const App::Property *_prop, bool restored) const {
|
||||
auto prop = Base::freecad_dynamic_cast<const PropertyComplexGeoData>(_prop);
|
||||
if(!prop)
|
||||
return std::string();
|
||||
return prop->getElementMapVersion(restored);
|
||||
}
|
||||
|
||||
bool DocumentObject::checkElementMapVersion(const App::Property *_prop, const char *ver) const {
|
||||
auto prop = Base::freecad_dynamic_cast<const PropertyComplexGeoData>(_prop);
|
||||
if(!prop)
|
||||
return false;
|
||||
return prop->checkElementMapVersion(ver);
|
||||
}
|
||||
|
||||
const std::string &DocumentObject::hiddenMarker() {
|
||||
static std::string marker("!hide");
|
||||
return marker;
|
||||
|
||||
@@ -290,6 +290,20 @@ public:
|
||||
bool testIfLinkDAGCompatible(App::PropertyLinkSubList &linksTo) const;
|
||||
bool testIfLinkDAGCompatible(App::PropertyLinkSub &linkTo) const;
|
||||
|
||||
/** Return the element map version of the geometry data stored in the given property
|
||||
*
|
||||
* @param prop: the geometry property to query for element map version
|
||||
* @param restored: whether to query for the restored element map version.
|
||||
* In case of version upgrade, the restored version may
|
||||
* be different from the current version.
|
||||
*
|
||||
* @return Return the element map version string.
|
||||
*/
|
||||
virtual std::string getElementMapVersion(const App::Property *prop, bool restored=false) const;
|
||||
|
||||
/// Return true to signal re-generation of geometry element names
|
||||
virtual bool checkElementMapVersion(const App::Property *prop, const char *ver) const;
|
||||
|
||||
public:
|
||||
/** mustExecute
|
||||
* We call this method to check if the object was modified to
|
||||
|
||||
@@ -24,10 +24,12 @@
|
||||
#include "PreCompiled.h"
|
||||
|
||||
#include <Base/Tools.h>
|
||||
|
||||
#include <Base/Interpreter.h>
|
||||
#include <App/DocumentObjectPy.h>
|
||||
#include "Application.h"
|
||||
#include "ComplexGeoData.h"
|
||||
#include "Document.h"
|
||||
#include "DocumentObject.h"
|
||||
#include "DocumentObserver.h"
|
||||
#include "GeoFeature.h"
|
||||
|
||||
@@ -199,6 +201,18 @@ bool DocumentObjectT::operator==(const DocumentObjectT &other) const {
|
||||
&& property == other.property;
|
||||
}
|
||||
|
||||
bool DocumentObjectT::operator<(const DocumentObjectT &other) const {
|
||||
if(getDocumentName() < other.getDocumentName())
|
||||
return true;
|
||||
if(getDocumentName() > other.getDocumentName())
|
||||
return false;
|
||||
if(getObjectName() < other.getObjectName())
|
||||
return true;
|
||||
if(getObjectName() > other.getObjectName())
|
||||
return false;
|
||||
return getPropertyName() < other.getPropertyName();
|
||||
}
|
||||
|
||||
Document* DocumentObjectT::getDocument() const
|
||||
{
|
||||
return GetApplication().getDocument(document.c_str());
|
||||
@@ -378,22 +392,34 @@ std::string SubObjectT::getNewElementName() const {
|
||||
return std::move(element.first);
|
||||
}
|
||||
|
||||
std::string SubObjectT::getOldElementName(int *index) const {
|
||||
std::pair<std::string, std::string> element;
|
||||
auto obj = getObject();
|
||||
if(!obj)
|
||||
std::string SubObjectT::getOldElementName(int *index, bool fallback) const {
|
||||
const char *elementName = Data::ComplexGeoData::findElementName(subname.c_str());
|
||||
if (!elementName || !elementName[0])
|
||||
return std::string();
|
||||
GeoFeature::resolveElement(obj,subname.c_str(),element);
|
||||
if(!index)
|
||||
return std::move(element.second);
|
||||
std::size_t pos = element.second.find_first_of("0123456789");
|
||||
if(pos == std::string::npos)
|
||||
*index = -1;
|
||||
else {
|
||||
*index = std::atoi(element.second.c_str()+pos);
|
||||
element.second.resize(pos);
|
||||
std::string name = Data::ComplexGeoData::oldElementName(elementName);
|
||||
if (name.empty()) {
|
||||
std::pair<std::string, std::string> element;
|
||||
auto obj = getObject();
|
||||
if(!obj)
|
||||
return std::string();
|
||||
GeoFeature::resolveElement(obj,subname.c_str(),element);
|
||||
if (!element.second.empty())
|
||||
name = std::move(element.second);
|
||||
else if (fallback && !element.first.empty())
|
||||
name = std::move(element.first);
|
||||
else
|
||||
return std::string();
|
||||
}
|
||||
return std::move(element.second);
|
||||
if(index) {
|
||||
std::size_t pos = name.find_first_of("0123456789");
|
||||
if(pos == std::string::npos)
|
||||
*index = -1;
|
||||
else {
|
||||
*index = std::atoi(name.c_str()+pos);
|
||||
name.resize(pos);
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
App::DocumentObject *SubObjectT::getSubObject() const {
|
||||
@@ -407,8 +433,8 @@ std::string SubObjectT::getSubObjectPython(bool force) const {
|
||||
if(!force && subname.empty())
|
||||
return getObjectPython();
|
||||
std::stringstream str;
|
||||
str << "(" << getObjectPython() << ",u'"
|
||||
<< Base::Tools::escapedUnicodeFromUtf8(subname.c_str()) << "')";
|
||||
str << "(" << getObjectPython() << ", '"
|
||||
<< Base::Tools::escapeEncodeString(normalized().subname) << "')";
|
||||
return str.str();
|
||||
}
|
||||
|
||||
|
||||
@@ -118,6 +118,8 @@ public:
|
||||
void operator=(const Property*);
|
||||
/*! Equality operator */
|
||||
bool operator==(const DocumentObjectT&) const;
|
||||
/*! Less than operator */
|
||||
bool operator<(const DocumentObjectT &other) const;
|
||||
|
||||
/*! Get a pointer to the document or 0 if it doesn't exist any more. */
|
||||
Document* getDocument() const;
|
||||
@@ -230,8 +232,10 @@ public:
|
||||
|
||||
/** Return the old style sub-element name
|
||||
* @param index: if given, then return the element type, and extract the index
|
||||
* @param fallback: if true, then fallback to new style element name if
|
||||
* there is no old style name.
|
||||
*/
|
||||
std::string getOldElementName(int *index=nullptr) const;
|
||||
std::string getOldElementName(int *index=nullptr, bool fallback=true) const;
|
||||
|
||||
/// Return the sub-object
|
||||
DocumentObject *getSubObject() const;
|
||||
|
||||
+129
-8
@@ -25,10 +25,15 @@
|
||||
|
||||
#include <App/GeoFeaturePy.h>
|
||||
|
||||
#include <Base/Console.h>
|
||||
#include <App/Link.h>
|
||||
#include "ComplexGeoData.h"
|
||||
#include "Document.h"
|
||||
#include "GeoFeature.h"
|
||||
#include "GeoFeatureGroupExtension.h"
|
||||
#include "ComplexGeoData.h"
|
||||
#include "MappedElement.h"
|
||||
|
||||
FC_LOG_LEVEL_INIT("GeoFeature",true,true)
|
||||
|
||||
using namespace App;
|
||||
|
||||
@@ -43,6 +48,8 @@ PROPERTY_SOURCE(App::GeoFeature, App::DocumentObject)
|
||||
GeoFeature::GeoFeature(void)
|
||||
{
|
||||
ADD_PROPERTY_TYPE(Placement,(Base::Placement()),nullptr,Prop_NoRecompute,nullptr);
|
||||
ADD_PROPERTY_TYPE(_ElementMapVersion,(""),"Base",
|
||||
(App::PropertyType)(Prop_Output|Prop_Hidden|Prop_Transient),"");
|
||||
}
|
||||
|
||||
GeoFeature::~GeoFeature(void)
|
||||
@@ -81,16 +88,48 @@ PyObject* GeoFeature::getPyObject(void)
|
||||
}
|
||||
|
||||
|
||||
std::pair<std::string,std::string> GeoFeature::getElementName(
|
||||
const char *name, ElementNameType type) const
|
||||
std::pair<std::string,std::string>
|
||||
GeoFeature::getElementName(const char *name, ElementNameType type) const
|
||||
{
|
||||
(void)type;
|
||||
|
||||
std::pair<std::string,std::string> ret;
|
||||
if(!name)
|
||||
if(!name)
|
||||
return ret;
|
||||
|
||||
ret.second = name;
|
||||
auto prop = getPropertyOfGeometry();
|
||||
if(!prop) return std::make_pair("", name);
|
||||
|
||||
auto geo = prop->getComplexData();
|
||||
if(!geo) return std::make_pair("", name);
|
||||
|
||||
return _getElementName(name, geo->getElementName(name));
|
||||
}
|
||||
|
||||
std::pair<std::string,std::string>
|
||||
GeoFeature::_getElementName(const char *name, const Data::MappedElement &mapped) const
|
||||
{
|
||||
std::pair<std::string,std::string> ret;
|
||||
if (mapped.index && mapped.name) {
|
||||
std::ostringstream ss;
|
||||
ss << Data::ComplexGeoData::elementMapPrefix()
|
||||
<< mapped.name << '.' << mapped.index;
|
||||
ret.first = ss.str();
|
||||
mapped.index.toString(ret.second);
|
||||
} else if (mapped.name) {
|
||||
FC_TRACE("element mapped name " << name << " not found in " << getFullName());
|
||||
ret.first = name;
|
||||
const char *dot = strrchr(name,'.');
|
||||
if(dot) {
|
||||
// deliberately mangle the old style element name to signal a
|
||||
// missing reference
|
||||
ret.second = Data::ComplexGeoData::missingPrefix();
|
||||
ret.second += dot+1;
|
||||
}
|
||||
} else {
|
||||
mapped.index.toString(ret.second);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -99,6 +138,8 @@ DocumentObject *GeoFeature::resolveElement(DocumentObject *obj, const char *subn
|
||||
ElementNameType type, const DocumentObject *filter,
|
||||
const char **_element, GeoFeature **geoFeature)
|
||||
{
|
||||
elementName.first.clear();
|
||||
elementName.second.clear();
|
||||
if(!obj || !obj->getNameInDocument())
|
||||
return nullptr;
|
||||
if(!subname)
|
||||
@@ -108,11 +149,16 @@ DocumentObject *GeoFeature::resolveElement(DocumentObject *obj, const char *subn
|
||||
auto sobj = obj->getSubObject(subname);
|
||||
if(!sobj)
|
||||
return nullptr;
|
||||
obj = sobj->getLinkedObject(true);
|
||||
auto geo = dynamic_cast<GeoFeature*>(obj);
|
||||
auto linked = sobj->getLinkedObject(true);
|
||||
auto geo = Base::freecad_dynamic_cast<GeoFeature>(linked);
|
||||
if(!geo && linked) {
|
||||
auto ext = linked->getExtensionByType<LinkBaseExtension>(true);
|
||||
if(ext)
|
||||
geo = Base::freecad_dynamic_cast<GeoFeature>(ext->getTrueLinkedObject(true));
|
||||
}
|
||||
if(geoFeature)
|
||||
*geoFeature = geo;
|
||||
if(!obj || (filter && obj!=filter))
|
||||
if(filter && geo!=filter)
|
||||
return nullptr;
|
||||
if(!element || !element[0]) {
|
||||
if(append)
|
||||
@@ -139,3 +185,78 @@ DocumentObject *GeoFeature::resolveElement(DocumentObject *obj, const char *subn
|
||||
return sobj;
|
||||
}
|
||||
|
||||
bool GeoFeature::hasMissingElement(const char *subname) {
|
||||
return Data::ComplexGeoData::hasMissingElement(subname);
|
||||
if(!subname)
|
||||
return false;
|
||||
auto dot = strrchr(subname,'.');
|
||||
if(!dot)
|
||||
return subname[0]=='?';
|
||||
return dot[1]=='?';
|
||||
}
|
||||
|
||||
void GeoFeature::updateElementReference() {
|
||||
auto prop = getPropertyOfGeometry();
|
||||
if(!prop) return;
|
||||
auto geo = prop->getComplexData();
|
||||
if(!geo) return;
|
||||
bool reset = false;
|
||||
auto version = getElementMapVersion(prop);
|
||||
if(_ElementMapVersion.getStrValue().empty())
|
||||
_ElementMapVersion.setValue(version);
|
||||
else if(_ElementMapVersion.getStrValue()!=version) {
|
||||
reset = true;
|
||||
_ElementMapVersion.setValue(version);
|
||||
}
|
||||
PropertyLinkBase::updateElementReferences(this,reset);
|
||||
}
|
||||
|
||||
void GeoFeature::onChanged(const Property *prop) {
|
||||
if(prop==getPropertyOfGeometry()) {
|
||||
if(getDocument() && !getDocument()->testStatus(Document::Restoring)
|
||||
&& !getDocument()->isPerformingTransaction())
|
||||
{
|
||||
updateElementReference();
|
||||
}
|
||||
}
|
||||
DocumentObject::onChanged(prop);
|
||||
}
|
||||
|
||||
void GeoFeature::onDocumentRestored() {
|
||||
if(!getDocument()->testStatus(Document::Status::Importing))
|
||||
_ElementMapVersion.setValue(getElementMapVersion(getPropertyOfGeometry(),true));
|
||||
DocumentObject::onDocumentRestored();
|
||||
}
|
||||
|
||||
const std::vector<std::string>&
|
||||
GeoFeature::searchElementCache(const std::string &element,
|
||||
bool checkGeometry,
|
||||
double tol,
|
||||
double atol) const
|
||||
{
|
||||
static std::vector<std::string> none;
|
||||
(void)element;
|
||||
(void)checkGeometry;
|
||||
(void)tol;
|
||||
(void)atol;
|
||||
return none;
|
||||
}
|
||||
|
||||
const std::vector<const char *>&
|
||||
GeoFeature::getElementTypes(bool /*all*/) const
|
||||
{
|
||||
static std::vector<const char *> nil;
|
||||
auto prop = getPropertyOfGeometry();
|
||||
if (!prop)
|
||||
return nil;
|
||||
return prop->getComplexData()->getElementTypes();
|
||||
}
|
||||
|
||||
std::vector<Data::IndexedName>
|
||||
GeoFeature::getHigherElements(const char *element, bool silent) const
|
||||
{
|
||||
auto prop = getPropertyOfGeometry();
|
||||
if (!prop)
|
||||
return {};
|
||||
return prop->getComplexData()->getHigherElements(element, silent);
|
||||
}
|
||||
|
||||
+55
-3
@@ -24,7 +24,9 @@
|
||||
#ifndef APP_GEOFEATURE_H
|
||||
#define APP_GEOFEATURE_H
|
||||
|
||||
#include <memory>
|
||||
#include "DocumentObject.h"
|
||||
#include "MappedElement.h"
|
||||
#include "PropertyGeo.h"
|
||||
|
||||
|
||||
@@ -40,6 +42,7 @@ class AppExport GeoFeature : public App::DocumentObject
|
||||
|
||||
public:
|
||||
PropertyPlacement Placement;
|
||||
PropertyString _ElementMapVersion;
|
||||
|
||||
/// Constructor
|
||||
GeoFeature(void);
|
||||
@@ -80,10 +83,17 @@ public:
|
||||
* @param name: input name
|
||||
* @param type: desired element name type to return
|
||||
*
|
||||
* @return a pair(newName,oldName). New element name may be empty.
|
||||
* This function relies on ComplexGeoData::elementMapPrefix() to decide
|
||||
* whether it is a forward query, i.e. mapped -> original, or reverse
|
||||
* query. The reason being that, unlike ComplexGeoData who deals with the
|
||||
* actual element map data, GeoFeature here sits at a higher level.
|
||||
* GeoFeature should be dealing with whatever various PropertyLinkSub(s) is
|
||||
* assigned.
|
||||
*
|
||||
* This function currently is does nothing. The new style element name
|
||||
* generation will be added in the next batch of patches.
|
||||
* This function is made virtual, so that inherited class can do something
|
||||
* unusual, such as Sketcher::SketcherObject, which uses this to expose its
|
||||
* private geometries without a correpsonding TopoShape, and yet being
|
||||
* source code compatible.
|
||||
*/
|
||||
virtual std::pair<std::string,std::string> getElementName(
|
||||
const char *name, ElementNameType type=Normal) const;
|
||||
@@ -107,6 +117,8 @@ public:
|
||||
bool append=false, ElementNameType type=Normal,
|
||||
const DocumentObject *filter=nullptr,const char **element=nullptr, GeoFeature **geo=nullptr);
|
||||
|
||||
static bool hasMissingElement(const char *subname);
|
||||
|
||||
/**
|
||||
* @brief Calculates the placement in the global reference coordinate system
|
||||
*
|
||||
@@ -120,6 +132,46 @@ public:
|
||||
* @return Base::Placement The transformation from the global reference coordinate system
|
||||
*/
|
||||
Base::Placement globalPlacement() const;
|
||||
|
||||
/** Search sub element using internal cached geometry
|
||||
*
|
||||
* @param element: element name
|
||||
* @param checkGeometry: search element by comparing geometry
|
||||
* @param tol: coordinate tolerance
|
||||
* @param atol: angle tolerance
|
||||
*
|
||||
* @return Returns a list of found element reference to the new goemetry.
|
||||
* The returned value will be invalidated when the geometry is changed.
|
||||
*
|
||||
* Before changing the property of geometry, GeoFeature will internally
|
||||
* make a snapshot of all referenced element geometry. After change, user
|
||||
* code may call this function to search for the new element name that
|
||||
* reference to the same geometry of the old element.
|
||||
*/
|
||||
virtual const std::vector<std::string>& searchElementCache(const std::string &element,
|
||||
bool checkGeometry = true,
|
||||
double tol = 1e-7,
|
||||
double atol = 1e-10) const;
|
||||
|
||||
|
||||
/// Return the object that owns the shape that contains the give element name
|
||||
virtual DocumentObject *getElementOwner(const Data::MappedName & /*name*/) const
|
||||
{return nullptr;}
|
||||
|
||||
virtual const std::vector<const char *>& getElementTypes(bool all=true) const;
|
||||
|
||||
/// Return the higher level element names of the given element
|
||||
virtual std::vector<Data::IndexedName> getHigherElements(const char *name, bool silent=false) const;
|
||||
|
||||
protected:
|
||||
virtual void onChanged(const Property* prop);
|
||||
virtual void onDocumentRestored();
|
||||
void updateElementReference();
|
||||
std::pair<std::string,std::string> _getElementName(const char *name, const Data::MappedElement &mapped) const;
|
||||
|
||||
private:
|
||||
std::vector<Data::MappedElement> _elementMapCache;
|
||||
std::string _elementMapVersion;
|
||||
};
|
||||
|
||||
} //namespace App
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
/****************************************************************************
|
||||
* Copyright (c) 2022 Zheng, Lei (realthunder) <[email protected]>*
|
||||
* *
|
||||
* This file is part of the FreeCAD CAx development system. *
|
||||
* *
|
||||
* This library is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU Library General Public *
|
||||
* License as published by the Free Software Foundation; either *
|
||||
* version 2 of the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Library General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Library General Public *
|
||||
* License along with this library; see the file COPYING.LIB. If not, *
|
||||
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
||||
* Suite 330, Boston, MA 02111-1307, USA *
|
||||
* *
|
||||
****************************************************************************/
|
||||
|
||||
#include "PreCompiled.h"
|
||||
|
||||
#ifndef _PreComp_
|
||||
# include <cstdlib>
|
||||
# include <unordered_set>
|
||||
#endif
|
||||
|
||||
#include <QHash>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include "DocumentObject.h"
|
||||
#include "MappedElement.h"
|
||||
|
||||
using namespace Data;
|
||||
|
||||
struct ByteArray
|
||||
{
|
||||
ByteArray(const QByteArray &b)
|
||||
:bytes(b)
|
||||
{}
|
||||
|
||||
ByteArray(const ByteArray &other)
|
||||
:bytes(other.bytes)
|
||||
{}
|
||||
|
||||
ByteArray(ByteArray &&other)
|
||||
:bytes(std::move(other.bytes))
|
||||
{}
|
||||
|
||||
void mutate() const
|
||||
{
|
||||
QByteArray copy;
|
||||
copy.append(bytes.constData(), bytes.size());
|
||||
bytes = copy;
|
||||
}
|
||||
|
||||
bool operator==(const ByteArray & other) const {
|
||||
return bytes == other.bytes;
|
||||
}
|
||||
|
||||
mutable QByteArray bytes;
|
||||
};
|
||||
|
||||
struct ByteArrayHasher
|
||||
{
|
||||
std::size_t operator()(const ByteArray &bytes) const
|
||||
{
|
||||
return qHash(bytes.bytes);
|
||||
}
|
||||
|
||||
std::size_t operator()(const QByteArray &bytes) const
|
||||
{
|
||||
return qHash(bytes);
|
||||
}
|
||||
};
|
||||
|
||||
void IndexedName::set(const char *name,
|
||||
int len,
|
||||
const std::vector<const char*> &types,
|
||||
bool allowOthers)
|
||||
{
|
||||
static std::unordered_set<ByteArray, ByteArrayHasher> NameSet;
|
||||
|
||||
if (len < 0)
|
||||
len = static_cast<int>(std::strlen(name));
|
||||
int i;
|
||||
for (i=len-1; i>=0; --i) {
|
||||
if (name[i]<'0' || name[i]>'9')
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
this->index = std::atoi(name+i);
|
||||
|
||||
for (int j=0; j<i; ++j) {
|
||||
if (name[j] == '_'
|
||||
|| (name[j] >= 'a' && name[j] <= 'z' )
|
||||
|| (name[j] >= 'A' && name[j] <= 'Z'))
|
||||
continue;
|
||||
this->type = "";
|
||||
return;
|
||||
}
|
||||
|
||||
for (const char * type : types) {
|
||||
int j=0;
|
||||
for (const char *n=name, *t=type; *n; ++n) {
|
||||
if (*n != *t || j >= i)
|
||||
break;
|
||||
++i;
|
||||
++t;
|
||||
if (!*t) {
|
||||
this->type = type;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allowOthers) {
|
||||
auto res = NameSet.insert(QByteArray::fromRawData(name, i));
|
||||
if (res.second)
|
||||
res.first->mutate();
|
||||
this->type = res.first->bytes.constData();
|
||||
} else
|
||||
this->type = "";
|
||||
}
|
||||
|
||||
void MappedName::compact() const
|
||||
{
|
||||
auto self = const_cast<MappedName*>(this);
|
||||
|
||||
if (this->raw) {
|
||||
self->data = QByteArray(self->data.constData(), self->data.size());
|
||||
self->raw = false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static std::unordered_set<QByteArray, ByteArrayHasher> PostfixSet;
|
||||
if (this->postfix.size()) {
|
||||
auto res = PostfixSet.insert(this->postfix);
|
||||
if (!res.second)
|
||||
self->postfix = *res.first;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ElementNameComp::operator()(const MappedName &a, const MappedName &b) const {
|
||||
size_t size = std::min(a.size(),b.size());
|
||||
if(!size)
|
||||
return a.size()<b.size();
|
||||
size_t i=0;
|
||||
if(b[0] == '#') {
|
||||
if(a[0]!='#')
|
||||
return true;
|
||||
// If both string starts with '#', compare the following hex digits by
|
||||
// its integer value.
|
||||
int res = 0;
|
||||
for(i=1;i<size;++i) {
|
||||
unsigned char ac = (unsigned char)a[i];
|
||||
unsigned char bc = (unsigned char)b[i];
|
||||
if(std::isxdigit(bc)) {
|
||||
if(!std::isxdigit(ac))
|
||||
return true;
|
||||
if(res==0) {
|
||||
if(ac<bc)
|
||||
res = -1;
|
||||
else if(ac>bc)
|
||||
res = 1;
|
||||
}
|
||||
}else if(std::isxdigit(ac))
|
||||
return false;
|
||||
else
|
||||
break;
|
||||
}
|
||||
if(res < 0)
|
||||
return true;
|
||||
else if(res > 0)
|
||||
return false;
|
||||
|
||||
for (; i<size; ++i) {
|
||||
char ac = a[i];
|
||||
char bc = b[i];
|
||||
if (ac < bc)
|
||||
return true;
|
||||
if (ac > bc)
|
||||
return false;
|
||||
}
|
||||
return a.size()<b.size();
|
||||
}
|
||||
else if (a[0] == '#')
|
||||
return false;
|
||||
|
||||
// If the string does not start with '#', compare the non-digits prefix
|
||||
// using lexical order.
|
||||
for(i=0;i<size;++i) {
|
||||
unsigned char ac = (unsigned char)a[i];
|
||||
unsigned char bc = (unsigned char)b[i];
|
||||
if(!std::isdigit(bc)) {
|
||||
if(std::isdigit(ac))
|
||||
return true;
|
||||
if(ac<bc)
|
||||
return true;
|
||||
if(ac>bc)
|
||||
return false;
|
||||
} else if(!std::isdigit(ac)) {
|
||||
return false;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
// Then compare the following digits part by integer value
|
||||
int res = 0;
|
||||
for(;i<size;++i) {
|
||||
unsigned char ac = (unsigned char)a[i];
|
||||
unsigned char bc = (unsigned char)b[i];
|
||||
if(std::isdigit(bc)) {
|
||||
if(!std::isdigit(ac))
|
||||
return true;
|
||||
if(res==0) {
|
||||
if(ac<bc)
|
||||
res = -1;
|
||||
else if(ac>bc)
|
||||
res = 1;
|
||||
}
|
||||
}else if(std::isdigit(ac))
|
||||
return false;
|
||||
else
|
||||
break;
|
||||
}
|
||||
if(res < 0)
|
||||
return true;
|
||||
else if(res > 0)
|
||||
return false;
|
||||
|
||||
// Finally, compare the remaining tail using lexical order
|
||||
for (; i<size; ++i) {
|
||||
char ac = a[i];
|
||||
char bc = b[i];
|
||||
if (ac < bc)
|
||||
return true;
|
||||
if (ac > bc)
|
||||
return false;
|
||||
}
|
||||
return a.size()<b.size();
|
||||
}
|
||||
|
||||
HistoryItem::HistoryItem(App::DocumentObject *obj, const Data::MappedName &name)
|
||||
:obj(obj),tag(0),element(name)
|
||||
{
|
||||
if(obj)
|
||||
tag = obj->getID();
|
||||
}
|
||||
|
||||
const std::string & MappedChildElements::prefix()
|
||||
{
|
||||
static std::string _prefix(ComplexGeoData::elementMapPrefix() + ":R");
|
||||
return _prefix;
|
||||
}
|
||||
Executable
+847
@@ -0,0 +1,847 @@
|
||||
/****************************************************************************
|
||||
* Copyright (c) 2022 Zheng, Lei (realthunder) <[email protected]>*
|
||||
* *
|
||||
* This file is part of the FreeCAD CAx development system. *
|
||||
* *
|
||||
* This library is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU Library General Public *
|
||||
* License as published by the Free Software Foundation; either *
|
||||
* version 2 of the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Library General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Library General Public *
|
||||
* License along with this library; see the file COPYING.LIB. If not, *
|
||||
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
||||
* Suite 330, Boston, MA 02111-1307, USA *
|
||||
* *
|
||||
****************************************************************************/
|
||||
|
||||
|
||||
#ifndef _AppMappedElement_h_
|
||||
#define _AppMappedElement_h_
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <cctype>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <QByteArray>
|
||||
#include <QHash>
|
||||
#include "ComplexGeoData.h"
|
||||
#include "StringHasher.h"
|
||||
|
||||
namespace App
|
||||
{
|
||||
class DocumentObject;
|
||||
}
|
||||
|
||||
namespace Data
|
||||
{
|
||||
|
||||
class AppExport IndexedName {
|
||||
public:
|
||||
explicit IndexedName(const char *name = nullptr, int _index = 0)
|
||||
: index(0)
|
||||
{
|
||||
if (!name)
|
||||
this->type = "";
|
||||
else {
|
||||
set(name);
|
||||
if (_index)
|
||||
this->index = _index;
|
||||
}
|
||||
}
|
||||
|
||||
IndexedName(const char *name,
|
||||
const std::vector<const char*> & types,
|
||||
bool allowOthers=true)
|
||||
{
|
||||
set(name, -1, types, allowOthers);
|
||||
}
|
||||
|
||||
explicit IndexedName(const QByteArray & data)
|
||||
{
|
||||
set(data.constData(), data.size());
|
||||
}
|
||||
|
||||
IndexedName(const IndexedName &other)
|
||||
: type(other.type), index(other.index)
|
||||
{}
|
||||
|
||||
static IndexedName fromConst(const char *name, int index) {
|
||||
IndexedName res;
|
||||
res.type = name;
|
||||
res.index = index;
|
||||
return res;
|
||||
}
|
||||
|
||||
IndexedName & operator=(const IndexedName & other)
|
||||
{
|
||||
this->index = other.index;
|
||||
this->type = other.type;
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend std::ostream & operator<<(std::ostream & s, const IndexedName & e)
|
||||
{
|
||||
s << e.type;
|
||||
if (e.index > 0)
|
||||
s << e.index;
|
||||
return s;
|
||||
}
|
||||
|
||||
bool operator==(const IndexedName & other) const
|
||||
{
|
||||
return this->index == other.index
|
||||
&& (this->type == other.type
|
||||
|| std::strcmp(this->type, other.type)==0);
|
||||
}
|
||||
|
||||
IndexedName & operator+=(int offset)
|
||||
{
|
||||
this->index += offset;
|
||||
assert(this->index >= 0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
IndexedName & operator++()
|
||||
{
|
||||
++this->index;
|
||||
return *this;
|
||||
}
|
||||
|
||||
IndexedName & operator--()
|
||||
{
|
||||
--this->index;
|
||||
assert(this->index >= 0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator!=(const IndexedName & other) const
|
||||
{
|
||||
return !(this->operator==(other));
|
||||
}
|
||||
|
||||
const char * toString(std::string & s) const
|
||||
{
|
||||
// Note! s is not cleared on purpose.
|
||||
std::size_t offset = s.size();
|
||||
s += this->type;
|
||||
if (this->index > 0)
|
||||
s += std::to_string(this->index);
|
||||
return s.c_str() + offset;
|
||||
}
|
||||
|
||||
int compare(const IndexedName & other) const
|
||||
{
|
||||
int res = std::strcmp(this->type, other.type);
|
||||
if (res)
|
||||
return res;
|
||||
if (this->index < other.index)
|
||||
return -1;
|
||||
if (this->index > other.index)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool operator<(const IndexedName & other) const
|
||||
{
|
||||
return compare(other) < 0;
|
||||
}
|
||||
|
||||
char operator[](int index) const
|
||||
{
|
||||
return this->type[index];
|
||||
}
|
||||
|
||||
const char * getType() const { return this->type; }
|
||||
|
||||
int getIndex() const { return this->index; }
|
||||
|
||||
void setIndex(int index) { assert(index>=0); this->index = index; }
|
||||
|
||||
bool isNull() const { return !this->type[0]; }
|
||||
|
||||
explicit operator bool() const { return !isNull(); }
|
||||
|
||||
protected:
|
||||
void set(const char *,
|
||||
int len = -1,
|
||||
const std::vector<const char *> &types = {},
|
||||
bool allowOthers = true);
|
||||
|
||||
private:
|
||||
const char * type;
|
||||
int index;
|
||||
};
|
||||
|
||||
class AppExport MappedName
|
||||
{
|
||||
public:
|
||||
MappedName()
|
||||
:raw(false)
|
||||
{}
|
||||
|
||||
#if QT_VERSION >= 0x050200
|
||||
MappedName(MappedName &&other)
|
||||
:data(std::move(other.data))
|
||||
,postfix(std::move(other.postfix))
|
||||
,raw(other.raw)
|
||||
{}
|
||||
|
||||
MappedName & operator=(MappedName &&other)
|
||||
{
|
||||
this->data = std::move(other.data);
|
||||
this->postfix = std::move(other.postfix);
|
||||
this->raw = other.raw;
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
explicit MappedName(const char * name, int size = -1)
|
||||
:raw(false)
|
||||
{
|
||||
if (!name) return;
|
||||
if (boost::starts_with(name, ComplexGeoData::elementMapPrefix()))
|
||||
name += ComplexGeoData::elementMapPrefix().size();
|
||||
if (size < 0)
|
||||
data = QByteArray(name);
|
||||
else
|
||||
data = QByteArray(name, size);
|
||||
}
|
||||
|
||||
explicit MappedName(const std::string & name)
|
||||
{
|
||||
int size = name.size();
|
||||
const char *n = name.c_str();
|
||||
if (boost::starts_with(name, ComplexGeoData::elementMapPrefix())) {
|
||||
n += ComplexGeoData::elementMapPrefix().size();
|
||||
size -= ComplexGeoData::elementMapPrefix().size();
|
||||
}
|
||||
data = QByteArray(n, size);
|
||||
}
|
||||
|
||||
explicit MappedName(const IndexedName & element)
|
||||
:data(element.getType()), raw(false)
|
||||
{
|
||||
if (element.getIndex() > 0)
|
||||
data += QByteArray::number(element.getIndex());
|
||||
}
|
||||
|
||||
explicit MappedName(const App::StringIDRef & sid)
|
||||
:raw(false)
|
||||
{
|
||||
sid.toBytes(this->data);
|
||||
}
|
||||
|
||||
MappedName(const MappedName & other)
|
||||
:data(other.data), postfix(other.postfix), raw(other.raw)
|
||||
{}
|
||||
|
||||
MappedName(const MappedName & other, int from, int size = -1)
|
||||
: raw(false)
|
||||
{
|
||||
append(other, from, size);
|
||||
}
|
||||
|
||||
MappedName(const MappedName & other, const char *postfix)
|
||||
:data(other.data + other.postfix)
|
||||
,postfix(postfix)
|
||||
,raw(false)
|
||||
{}
|
||||
|
||||
static MappedName fromRawData(const char * name, int size = -1)
|
||||
{
|
||||
MappedName res;
|
||||
if (name) {
|
||||
res.data = QByteArray::fromRawData(name,
|
||||
size>=0 ? size: qstrlen(name));
|
||||
res.raw = true;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static MappedName fromRawData(const QByteArray & data)
|
||||
{
|
||||
return fromRawData(data.constData(), data.size());
|
||||
}
|
||||
|
||||
static MappedName fromRawData(const MappedName &other, int from, int size = -1)
|
||||
{
|
||||
if (from < 0)
|
||||
from = 0;
|
||||
|
||||
if (from >= other.size())
|
||||
return MappedName();
|
||||
|
||||
if (from >= other.data.size())
|
||||
return MappedName(other, from, size);
|
||||
|
||||
MappedName res;
|
||||
res.raw = true;
|
||||
if (size < 0)
|
||||
size = other.size() - from;
|
||||
|
||||
if (size < other.data.size()-from)
|
||||
res.data = QByteArray::fromRawData(
|
||||
other.data.constData()+from, size);
|
||||
else {
|
||||
res.data = QByteArray::fromRawData(
|
||||
other.data.constData()+from, other.data.size()-from);
|
||||
size -= other.data.size() - from;
|
||||
if (size == other.postfix.size())
|
||||
res.postfix = other.postfix;
|
||||
else if (size)
|
||||
res.postfix.append(other.postfix.constData(), size);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
MappedName & operator=(const MappedName & other)
|
||||
{
|
||||
this->data = other.data;
|
||||
this->postfix = other.postfix;
|
||||
this->raw = other.raw;
|
||||
return *this;
|
||||
}
|
||||
|
||||
MappedName & operator=(const std::string & other)
|
||||
{
|
||||
*this = MappedName(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
MappedName & operator=(const char * other)
|
||||
{
|
||||
*this = MappedName(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend std::ostream & operator<<(std::ostream & s, const MappedName & n)
|
||||
{
|
||||
s.write(n.data.constData(), n.data.size());
|
||||
s.write(n.postfix.constData(), n.postfix.size());
|
||||
return s;
|
||||
}
|
||||
|
||||
bool operator==(const MappedName & other) const
|
||||
{
|
||||
if (this->size() != other.size())
|
||||
return false;
|
||||
if (this->data.size() == other.data.size())
|
||||
return this->data == other.data && this->postfix == other.postfix;
|
||||
const auto &a = this->data.size() < other.data.size() ? *this : other;
|
||||
const auto &b = this->data.size() < other.data.size() ? other: *this;
|
||||
if (!b.data.startsWith(a.data))
|
||||
return false;
|
||||
QByteArray tmp = QByteArray::fromRawData(
|
||||
b.data.constData() + a.data.size(),
|
||||
b.data.size() - a.data.size());
|
||||
if (!a.postfix.startsWith(tmp))
|
||||
return false;
|
||||
tmp = QByteArray::fromRawData(
|
||||
a.postfix.constData() + tmp.size(),
|
||||
a.postfix.size() - tmp.size());
|
||||
return tmp == b.postfix;
|
||||
}
|
||||
|
||||
bool operator!=(const MappedName & other) const
|
||||
{
|
||||
return !(this->operator==(other));
|
||||
}
|
||||
|
||||
MappedName operator+(const MappedName & other) const
|
||||
{
|
||||
MappedName res(*this);
|
||||
res += other;
|
||||
return res;
|
||||
}
|
||||
|
||||
MappedName operator+(const char * other) const
|
||||
{
|
||||
MappedName res(*this);
|
||||
res += other;
|
||||
return res;
|
||||
}
|
||||
|
||||
MappedName operator+(const std::string & other) const
|
||||
{
|
||||
MappedName res(*this);
|
||||
res += other;
|
||||
return res;
|
||||
}
|
||||
|
||||
MappedName operator+(const QByteArray & other) const
|
||||
{
|
||||
MappedName res(*this);
|
||||
res += other;
|
||||
return res;
|
||||
}
|
||||
|
||||
MappedName & operator+=(const char * other)
|
||||
{
|
||||
if (other && other[0])
|
||||
this->postfix.append(other, -1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
MappedName & operator+=(const std::string & other)
|
||||
{
|
||||
if (other.size()) {
|
||||
this->postfix.reserve(this->postfix.size() + other.size());
|
||||
this->postfix.append(other.c_str(), other.size());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
MappedName & operator+=(const QByteArray & other)
|
||||
{
|
||||
this->postfix += other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
MappedName & operator+=(const MappedName & other)
|
||||
{
|
||||
append(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void append(const char * d, int size = -1)
|
||||
{
|
||||
if (d && size) {
|
||||
if (size < 0)
|
||||
size = qstrlen(d);
|
||||
if (empty())
|
||||
this->data.append(d, size);
|
||||
else
|
||||
this->postfix.append(d, size);
|
||||
}
|
||||
}
|
||||
|
||||
void append(const MappedName & other, int from = 0, int size = -1)
|
||||
{
|
||||
if (from < 0)
|
||||
from = 0;
|
||||
else if (from > other.size())
|
||||
return;
|
||||
if (size < 0 || size + from > other.size())
|
||||
size = other.size() - from;
|
||||
|
||||
int count = size;
|
||||
if (from < other.data.size()) {
|
||||
if (count > other.data.size() - from)
|
||||
count = other.data.size() - from;
|
||||
if (from == 0 && count == other.data.size() && this->empty()) {
|
||||
this->data = other.data;
|
||||
this->raw = other.raw;
|
||||
} else
|
||||
append(other.data.constData() + from, count);
|
||||
from = 0;
|
||||
size -= count;
|
||||
} else
|
||||
from -= other.data.size();
|
||||
if (size) {
|
||||
if (from == 0 && size == other.postfix.size()) {
|
||||
if (this->empty())
|
||||
this->data = other.postfix;
|
||||
else if (this->postfix.isEmpty())
|
||||
this->postfix = other.postfix;
|
||||
else
|
||||
this->postfix += other.postfix;
|
||||
} else
|
||||
append(other.postfix.constData() + from, size);
|
||||
}
|
||||
}
|
||||
|
||||
std::string toString(int from, int len=-1) const
|
||||
{
|
||||
std::string res;
|
||||
return toString(res, from, len);
|
||||
}
|
||||
|
||||
const char * toString(std::string &s, int from=0, int len=-1) const
|
||||
{
|
||||
std::size_t offset = s.size();
|
||||
int count = this->size();
|
||||
if (from < 0)
|
||||
from = 0;
|
||||
else if (from >= count)
|
||||
return s.c_str()+s.size();
|
||||
if (len < 0 || len > count - from)
|
||||
len = count - from;
|
||||
s.reserve(s.size() + len);
|
||||
if (from < this->data.size()) {
|
||||
count = this->data.size() - from;
|
||||
if (len < count)
|
||||
count = len;
|
||||
s.append(this->data.constData()+from, count);
|
||||
len -= count;
|
||||
}
|
||||
s.append(this->postfix.constData(), len);
|
||||
return s.c_str() + offset;
|
||||
}
|
||||
|
||||
const char * toConstString(int offset, int &size) const
|
||||
{
|
||||
if (offset < 0)
|
||||
offset = 0;
|
||||
if (offset > this->data.size()) {
|
||||
offset -= this->data.size();
|
||||
if (offset > this->postfix.size()) {
|
||||
size = 0;
|
||||
return "";
|
||||
}
|
||||
size = this->postfix.size() - offset;
|
||||
return this->postfix.constData() + offset;
|
||||
}
|
||||
size = this->data.size() - offset;
|
||||
return this->data.constData() + offset;
|
||||
}
|
||||
|
||||
QByteArray toRawBytes(int offset=0, int size=-1) const
|
||||
{
|
||||
if (offset < 0)
|
||||
offset = 0;
|
||||
if (offset >= this->size())
|
||||
return QByteArray();
|
||||
if (size < 0 || size > this->size() - offset)
|
||||
size = this->size() - offset;
|
||||
if (offset >= this->data.size()) {
|
||||
offset -= this->data.size();
|
||||
return QByteArray::fromRawData(this->postfix.constData()+offset, size);
|
||||
}
|
||||
if (size <= this->data.size() - offset)
|
||||
return QByteArray::fromRawData(this->data.constData()+offset, size);
|
||||
|
||||
QByteArray res(this->data.constData()+offset, this->data.size()-offset);
|
||||
res.append(this->postfix.constData(), size - this->data.size() + offset);
|
||||
return res;
|
||||
}
|
||||
|
||||
const QByteArray & dataBytes() const
|
||||
{
|
||||
return this->data;
|
||||
}
|
||||
|
||||
const QByteArray & postfixBytes() const
|
||||
{
|
||||
return this->postfix;
|
||||
}
|
||||
|
||||
const char * constPostfix() const
|
||||
{
|
||||
return this->postfix.constData();
|
||||
}
|
||||
|
||||
// No constData() because 'data' is allow to contain raw data, which may
|
||||
// not end with 0.
|
||||
#if 0
|
||||
void char * constData() const
|
||||
{
|
||||
return this->data.constData();
|
||||
}
|
||||
#endif
|
||||
|
||||
QByteArray toBytes() const
|
||||
{
|
||||
if (this->postfix.isEmpty())
|
||||
return this->data;
|
||||
if (this->data.isEmpty())
|
||||
return this->postfix;
|
||||
return this->data + this->postfix;
|
||||
}
|
||||
|
||||
IndexedName toIndexedName() const
|
||||
{
|
||||
if (this->postfix.isEmpty())
|
||||
return IndexedName(this->data);
|
||||
return IndexedName();
|
||||
}
|
||||
|
||||
std::string toPrefixedString() const
|
||||
{
|
||||
std::string res;
|
||||
toPrefixedString(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
const char *toPrefixedString(std::string &buf) const
|
||||
{
|
||||
if (!toIndexedName())
|
||||
buf += ComplexGeoData::elementMapPrefix();
|
||||
toString(buf);
|
||||
return buf.c_str();
|
||||
}
|
||||
|
||||
int compare(const MappedName &other) const
|
||||
{
|
||||
int asize = this->size();
|
||||
int bsize = other.size();
|
||||
for (int i=0, count=std::min(asize, bsize); i<count; ++i) {
|
||||
char a = this->operator[](i);
|
||||
char b = other[i];
|
||||
if (a < b)
|
||||
return -1;
|
||||
if (a > b)
|
||||
return 1;
|
||||
}
|
||||
if (asize < bsize)
|
||||
return -1;
|
||||
if (asize > bsize)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool operator<(const MappedName & other) const
|
||||
{
|
||||
return compare(other) < 0;
|
||||
}
|
||||
|
||||
char operator[](int index) const
|
||||
{
|
||||
if (index >= this->data.size())
|
||||
return this->postfix[index - this->data.size()];
|
||||
return this->data[index];
|
||||
}
|
||||
|
||||
int size() const
|
||||
{
|
||||
return this->data.size() + this->postfix.size();
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return this->data.isEmpty() && this->postfix.isEmpty();
|
||||
}
|
||||
|
||||
bool isRaw() const
|
||||
{
|
||||
return this->raw;
|
||||
}
|
||||
|
||||
MappedName copy() const
|
||||
{
|
||||
if (!this->raw)
|
||||
return *this;
|
||||
MappedName res;
|
||||
res.data.append(this->data.constData(), this->data.size());
|
||||
res.postfix = this->postfix;
|
||||
return res;
|
||||
}
|
||||
|
||||
void compact() const;
|
||||
|
||||
explicit operator bool() const
|
||||
{
|
||||
return !empty();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
this->data.clear();
|
||||
this->postfix.clear();
|
||||
this->raw = false;
|
||||
}
|
||||
|
||||
int find(const char *d, int from = 0) const
|
||||
{
|
||||
if (!d)
|
||||
return -1;
|
||||
if (from < 0)
|
||||
from = 0;
|
||||
if (from < this->data.size()) {
|
||||
int res = this->data.indexOf(d, from);
|
||||
if (res >= 0)
|
||||
return res;
|
||||
from = 0;
|
||||
} else
|
||||
from -= this->data.size();
|
||||
int res = this->postfix.indexOf(d, from);
|
||||
if (res < 0)
|
||||
return res;
|
||||
return res + this->data.size();
|
||||
}
|
||||
|
||||
int find(const std::string &d, int from = 0) const
|
||||
{
|
||||
return find(d.c_str(), from);
|
||||
}
|
||||
|
||||
int rfind(const char *d, int from = -1) const
|
||||
{
|
||||
if (!d)
|
||||
return -1;
|
||||
if (from < 0 || from > this->postfix.size()) {
|
||||
if (from > postfix.size())
|
||||
from -= postfix.size();
|
||||
int res = this->postfix.lastIndexOf(d, from);
|
||||
if (res >= 0)
|
||||
return res + this->data.size();
|
||||
from = -1;
|
||||
}
|
||||
return this->data.lastIndexOf(d, from);
|
||||
}
|
||||
|
||||
int rfind(const std::string &d, int from = -1) const
|
||||
{
|
||||
return rfind(d.c_str(), from);
|
||||
}
|
||||
|
||||
bool endsWith(const char *s) const
|
||||
{
|
||||
if (!s)
|
||||
return false;
|
||||
if (this->postfix.size())
|
||||
return this->postfix.endsWith(s);
|
||||
return this->data.endsWith(s);
|
||||
}
|
||||
|
||||
bool endsWith(const std::string &s) const
|
||||
{
|
||||
return endsWith(s.c_str());
|
||||
}
|
||||
|
||||
bool startsWith(const QByteArray & s, int offset = 0) const
|
||||
{
|
||||
if (s.size() > size() - offset)
|
||||
return false;
|
||||
if (offset || (this->data.size() && this->data.size() < s.size()))
|
||||
return toRawBytes(offset, s.size()) == s;
|
||||
if (this->data.size())
|
||||
return this->data.startsWith(s);
|
||||
return this->postfix.startsWith(s);
|
||||
}
|
||||
|
||||
bool startsWith(const char *s, int offset = 0) const
|
||||
{
|
||||
if (!s)
|
||||
return false;
|
||||
return startsWith(QByteArray::fromRawData(s, qstrlen(s)), offset);
|
||||
}
|
||||
|
||||
bool startsWith(const std::string &s, int offset = 0) const
|
||||
{
|
||||
return startsWith(QByteArray::fromRawData(s.c_str(), s.size()), offset);
|
||||
}
|
||||
|
||||
std::size_t hash() const
|
||||
{
|
||||
#if QT_VERSION >= 0x050000
|
||||
return qHash(data, qHash(postfix));
|
||||
#else
|
||||
return qHash(data) ^ qHash(postfix);
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
QByteArray data;
|
||||
QByteArray postfix;
|
||||
bool raw;
|
||||
};
|
||||
|
||||
struct AppExport MappedElement
|
||||
{
|
||||
IndexedName index;
|
||||
MappedName name;
|
||||
|
||||
MappedElement()
|
||||
{}
|
||||
|
||||
MappedElement(const IndexedName & idx, const MappedName & n)
|
||||
: index(idx), name(n)
|
||||
{}
|
||||
|
||||
MappedElement(const MappedName & n, const IndexedName & idx)
|
||||
: index(idx), name(n)
|
||||
{}
|
||||
|
||||
MappedElement(const MappedElement & other)
|
||||
: index(other.index), name(other.name)
|
||||
{}
|
||||
|
||||
MappedElement(MappedElement && other)
|
||||
: index(std::move(other.index)), name(std::move(other.name))
|
||||
{}
|
||||
|
||||
MappedElement & operator=(MappedElement && other)
|
||||
{
|
||||
this->index = std::move(other.index);
|
||||
this->name = std::move(other.name);
|
||||
return *this;
|
||||
}
|
||||
|
||||
MappedElement & operator=(const MappedElement & other)
|
||||
{
|
||||
this->index = other.index;
|
||||
this->name = other.name;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const MappedElement &other) const
|
||||
{
|
||||
return this->index == other.index && this->name == other.name;
|
||||
}
|
||||
|
||||
bool operator!=(const MappedElement &other) const
|
||||
{
|
||||
return this->index != other.index || this->name != other.name;
|
||||
}
|
||||
|
||||
bool operator<(const MappedElement &other) const
|
||||
{
|
||||
int res = this->index.compare(other.index);
|
||||
if (res < 0)
|
||||
return true;
|
||||
if (res > 0)
|
||||
return false;
|
||||
return this->name < other.name;
|
||||
}
|
||||
};
|
||||
|
||||
struct AppExport HistoryItem {
|
||||
App::DocumentObject *obj;
|
||||
long tag;
|
||||
Data::MappedName element;
|
||||
Data::IndexedName index;
|
||||
std::vector<Data::MappedName> intermediates;
|
||||
HistoryItem(App::DocumentObject *obj, const Data::MappedName &name);
|
||||
};
|
||||
|
||||
struct AppExport ElementNameComp {
|
||||
/** Comparison function to make topo name more stable
|
||||
*
|
||||
* The sorting decompose the name into either of the following two forms
|
||||
* '#' + hex_digits + tail
|
||||
* non_digits + digits + tail
|
||||
*
|
||||
* The non-digits part is compared lexically, while the digits part is
|
||||
* compared by its integer value.
|
||||
*
|
||||
* The reason for this is to prevent name with bigger digits (usually means
|
||||
* comes late in history) comes early when sorting.
|
||||
*/
|
||||
bool operator()(const MappedName &a, const MappedName &b) const;
|
||||
};
|
||||
|
||||
typedef QVector<App::StringIDRef> ElementIDRefs;
|
||||
|
||||
struct AppExport MappedChildElements
|
||||
{
|
||||
IndexedName indexedName;
|
||||
int count;
|
||||
int offset;
|
||||
long tag;
|
||||
ElementMapPtr elementMap;
|
||||
QByteArray postfix;
|
||||
ElementIDRefs sids;
|
||||
|
||||
static const std::string & prefix();
|
||||
};
|
||||
|
||||
} //namespace Data
|
||||
|
||||
|
||||
#endif
|
||||
@@ -34,6 +34,7 @@
|
||||
#include <CXX/Objects.hxx>
|
||||
|
||||
#include "Application.h"
|
||||
#include "Document.h"
|
||||
#include "DocumentObject.h"
|
||||
#include "Property.h"
|
||||
#include "ObjectIdentifier.h"
|
||||
@@ -80,6 +81,12 @@ bool Property::isValidName(const char* name)
|
||||
return name && name[0] != '\0';
|
||||
}
|
||||
|
||||
void Property::SetRestoreError(const char * msg)
|
||||
{
|
||||
if (auto doc = father->getOwnerDocument())
|
||||
doc->setErrorDescription(this, msg);
|
||||
}
|
||||
|
||||
std::string Property::getFullName(bool python) const {
|
||||
if(!myName || (python && !father))
|
||||
return std::string(python?"None":"?");
|
||||
|
||||
@@ -254,6 +254,8 @@ public:
|
||||
/// Compare if this property has the same content as the given one
|
||||
virtual bool isSame(const Property &other) const;
|
||||
|
||||
virtual void SetRestoreError(const char *) override;
|
||||
|
||||
/** Return a unique ID for the property
|
||||
*
|
||||
* The ID of a property is generated from a monotonically increasing
|
||||
|
||||
+59
-3
@@ -23,19 +23,30 @@
|
||||
|
||||
#include "PreCompiled.h"
|
||||
|
||||
#include <Base/MatrixPy.h>
|
||||
#include <Base/PlacementPy.h>
|
||||
#include <Base/Reader.h>
|
||||
#ifndef _PreComp_
|
||||
# include <assert.h>
|
||||
#endif
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
/// Here the FreeCAD includes sorted by Base,App,Gui......
|
||||
|
||||
#include <Base/Exception.h>
|
||||
#include <Base/MatrixPy.h>
|
||||
#include <Base/Reader.h>
|
||||
#include <Base/PlacementPy.h>
|
||||
#include <Base/Quantity.h>
|
||||
#include <Base/QuantityPy.h>
|
||||
#include <Base/Rotation.h>
|
||||
#include <Base/RotationPy.h>
|
||||
#include <Base/Stream.h>
|
||||
#include <Base/Tools.h>
|
||||
#include <Base/Writer.h>
|
||||
#include <Base/VectorPy.h>
|
||||
#include <Base/Writer.h>
|
||||
|
||||
#include "Document.h"
|
||||
#include "DocumentObject.h"
|
||||
#include "PropertyGeo.h"
|
||||
#include "Placement.h"
|
||||
#include "ObjectIdentifier.h"
|
||||
@@ -1136,3 +1147,48 @@ PropertyComplexGeoData::~PropertyComplexGeoData()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
std::string PropertyComplexGeoData::getElementMapVersion(bool) const {
|
||||
auto data = getComplexData();
|
||||
if(!data)
|
||||
return std::string();
|
||||
auto owner = Base::freecad_dynamic_cast<DocumentObject>(getContainer());
|
||||
std::ostringstream ss;
|
||||
if(owner && owner->getDocument()
|
||||
&& owner->getDocument()->getStringHasher()==data->Hasher)
|
||||
ss << "1.";
|
||||
else
|
||||
ss << "0.";
|
||||
ss << data->getElementMapVersion();
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
bool PropertyComplexGeoData::checkElementMapVersion(const char * ver) const
|
||||
{
|
||||
auto data = getComplexData();
|
||||
if(!data)
|
||||
return false;
|
||||
auto owner = Base::freecad_dynamic_cast<DocumentObject>(getContainer());
|
||||
std::ostringstream ss;
|
||||
const char *prefix;
|
||||
if(owner && owner->getDocument()
|
||||
&& owner->getDocument()->getStringHasher() == data->Hasher)
|
||||
prefix = "1.";
|
||||
else
|
||||
prefix = "0.";
|
||||
if (!boost::starts_with(ver, prefix))
|
||||
return true;
|
||||
return data->checkElementMapVersion(ver+2);
|
||||
}
|
||||
|
||||
void PropertyComplexGeoData::afterRestore()
|
||||
{
|
||||
auto data = getComplexData();
|
||||
if (data && data->isRestoreFailed()) {
|
||||
data->resetRestoreFailure();
|
||||
auto owner = Base::freecad_dynamic_cast<DocumentObject>(getContainer());
|
||||
if (owner && owner->getDocument() && !owner->getDocument()->testStatus(App::Document::PartialDoc))
|
||||
owner->getDocument()->addRecomputeObject(owner);
|
||||
}
|
||||
PropertyGeometry::afterRestore();
|
||||
}
|
||||
|
||||
@@ -541,6 +541,18 @@ public:
|
||||
virtual const Data::ComplexGeoData* getComplexData() const = 0;
|
||||
virtual Base::BoundBox3d getBoundingBox() const = 0;
|
||||
//@}
|
||||
|
||||
/** Return the element map version
|
||||
*
|
||||
* @param persisted: if true, return the restored element map version. Or
|
||||
* else, return the current element map version
|
||||
*/
|
||||
virtual std::string getElementMapVersion(bool restored=false) const;
|
||||
|
||||
/// Return true to signal element map version change
|
||||
virtual bool checkElementMapVersion(const char * ver) const;
|
||||
|
||||
virtual void afterRestore();
|
||||
};
|
||||
|
||||
} // namespace App
|
||||
|
||||
+340
-109
@@ -26,21 +26,28 @@
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/iostreams/device/array.hpp>
|
||||
#include <boost/iostreams/stream.hpp>
|
||||
|
||||
#include <Base/Console.h>
|
||||
#include <Base/Exception.h>
|
||||
#include <Base/Reader.h>
|
||||
#include <Base/Writer.h>
|
||||
#include <Base/Console.h>
|
||||
#include <Base/Exception.h>
|
||||
#include <Base/Tools.h>
|
||||
|
||||
#include "PropertyLinks.h"
|
||||
#include "Application.h"
|
||||
#include "Document.h"
|
||||
#include "DocumentObject.h"
|
||||
#include "DocumentObjectPy.h"
|
||||
#include "DocumentObserver.h"
|
||||
#include "GeoFeature.h"
|
||||
#include "ObjectIdentifier.h"
|
||||
|
||||
|
||||
FC_LOG_LEVEL_INIT("PropertyLinks",true,true)
|
||||
FC_LOG_LEVEL_INIT("App",true,true)
|
||||
|
||||
using namespace App;
|
||||
using namespace Base;
|
||||
@@ -55,6 +62,9 @@ namespace bp = boost::placeholders;
|
||||
TYPESYSTEM_SOURCE_ABSTRACT(App::PropertyLinkBase , App::Property)
|
||||
|
||||
static std::unordered_map<std::string, std::set<PropertyLinkBase*> > _LabelMap;
|
||||
|
||||
static std::unordered_map<App::DocumentObject *, std::unordered_set<PropertyLinkBase*> > _ElementRefMap;
|
||||
|
||||
PropertyLinkBase::PropertyLinkBase()
|
||||
{}
|
||||
|
||||
@@ -67,6 +77,11 @@ void PropertyLinkBase::setAllowExternal(bool allow) {
|
||||
setFlag(LinkAllowExternal,allow);
|
||||
}
|
||||
|
||||
void PropertyLinkBase::setReturnNewElement(bool enable)
|
||||
{
|
||||
setFlag(LinkNewElement, enable);
|
||||
}
|
||||
|
||||
void PropertyLinkBase::hasSetValue() {
|
||||
auto owner = dynamic_cast<DocumentObject*>(getContainer());
|
||||
if(owner)
|
||||
@@ -98,6 +113,15 @@ bool PropertyLinkBase::isSame(const Property &other) const
|
||||
}
|
||||
|
||||
void PropertyLinkBase::unregisterElementReference() {
|
||||
for(auto obj : _ElementRefs) {
|
||||
auto it = _ElementRefMap.find(obj);
|
||||
if(it != _ElementRefMap.end()) {
|
||||
it->second.erase(this);
|
||||
if(it->second.empty())
|
||||
_ElementRefMap.erase(it);
|
||||
}
|
||||
}
|
||||
_ElementRefs.clear();
|
||||
}
|
||||
|
||||
void PropertyLinkBase::unregisterLabelReferences()
|
||||
@@ -202,39 +226,76 @@ static std::string propertyName(const Property *prop) {
|
||||
return prop->getFullName();
|
||||
}
|
||||
|
||||
std::vector<App::SubObjectT>
|
||||
PropertyLinkBase::linkedElementsT(bool all) const
|
||||
{
|
||||
std::vector<App::DocumentObject*> objs;
|
||||
std::vector<std::string> subs;
|
||||
getLinks(objs,all,&subs,true);
|
||||
std::vector<App::SubObjectT> res;
|
||||
res.reserve(objs.size());
|
||||
assert(objs.size() == subs.size());
|
||||
for (unsigned i=0; i<objs.size(); ++i)
|
||||
res.emplace_back(objs[i], subs[i].c_str());
|
||||
return res;
|
||||
}
|
||||
|
||||
const std::unordered_set<PropertyLinkBase*>&
|
||||
PropertyLinkBase::getElementReferences(DocumentObject *feature)
|
||||
{
|
||||
static std::unordered_set<PropertyLinkBase*> none;
|
||||
|
||||
auto it = _ElementRefMap.find(feature);
|
||||
if(it == _ElementRefMap.end())
|
||||
return none;
|
||||
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void PropertyLinkBase::updateElementReferences(DocumentObject *feature, bool reverse) {
|
||||
(void)feature;
|
||||
(void)reverse;
|
||||
if(!feature || !feature->getNameInDocument())
|
||||
return;
|
||||
auto it = _ElementRefMap.find(feature);
|
||||
if(it == _ElementRefMap.end())
|
||||
return;
|
||||
std::vector<PropertyLinkBase*> props;
|
||||
props.reserve(it->second.size());
|
||||
props.insert(props.end(),it->second.begin(),it->second.end());
|
||||
for(auto prop : props) {
|
||||
if(prop->getContainer()) {
|
||||
try {
|
||||
prop->updateElementReference(feature,reverse,true);
|
||||
}catch(Base::Exception &e) {
|
||||
e.ReportException();
|
||||
FC_ERR("Failed to update element reference of " << propertyName(prop));
|
||||
}catch(std::exception &e) {
|
||||
FC_ERR("Failed to update element reference of " << propertyName(prop)
|
||||
<< ": " << e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyLinkBase::_registerElementReference(App::DocumentObject *obj, std::string &sub, ShadowSub &shadow)
|
||||
{
|
||||
(void)obj;
|
||||
(void)sub;
|
||||
(void)shadow;
|
||||
if(!obj || !obj->getNameInDocument() || sub.empty())
|
||||
return;
|
||||
if(shadow.first.empty()) {
|
||||
_updateElementReference(0,obj,sub,shadow,false);
|
||||
return;
|
||||
}
|
||||
GeoFeature *geo = 0;
|
||||
const char *element = 0;
|
||||
std::pair<std::string, std::string> elementName;
|
||||
GeoFeature::resolveElement(obj,sub.c_str(), elementName,true,
|
||||
GeoFeature::ElementNameType::Export,0,&element,&geo);
|
||||
if(!geo || !element || !element[0])
|
||||
return;
|
||||
|
||||
if(_ElementRefs.insert(geo).second)
|
||||
_ElementRefMap[geo].insert(this);
|
||||
}
|
||||
|
||||
class StringGuard {
|
||||
public:
|
||||
StringGuard(char *c)
|
||||
:c(c)
|
||||
{
|
||||
v1 = c[0];
|
||||
v2 = c[1];
|
||||
c[0] = '.';
|
||||
c[1] = 0;
|
||||
}
|
||||
~StringGuard()
|
||||
{
|
||||
c[0] = v1;
|
||||
c[1] = v2;
|
||||
}
|
||||
|
||||
char *c;
|
||||
char v1;
|
||||
char v2;
|
||||
};
|
||||
|
||||
void PropertyLinkBase::restoreLabelReference(const DocumentObject *obj,
|
||||
std::string &subname, ShadowSub *shadow)
|
||||
{
|
||||
@@ -276,12 +337,136 @@ bool PropertyLinkBase::_updateElementReference(DocumentObject *feature,
|
||||
App::DocumentObject *obj, std::string &sub, ShadowSub &shadow,
|
||||
bool reverse, bool notify)
|
||||
{
|
||||
(void)feature;
|
||||
(void)obj;
|
||||
(void)reverse;
|
||||
(void)notify;
|
||||
shadow.second = sub;
|
||||
return false;
|
||||
if(!obj || !obj->getNameInDocument()) return false;
|
||||
ShadowSub elementName;
|
||||
const char *subname;
|
||||
if(shadow.first.size())
|
||||
subname = shadow.first.c_str();
|
||||
else if(shadow.second.size())
|
||||
subname = shadow.second.c_str();
|
||||
else
|
||||
subname = sub.c_str();
|
||||
GeoFeature *geo = 0;
|
||||
const char *element=0;
|
||||
auto ret = GeoFeature::resolveElement(obj,subname, elementName,true,
|
||||
GeoFeature::ElementNameType::Export,feature,&element,&geo);
|
||||
if(!ret || !geo || !element || !element[0]) {
|
||||
if(elementName.second.size())
|
||||
shadow.second.swap(elementName.second);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(_ElementRefs.insert(geo).second)
|
||||
_ElementRefMap[geo].insert(this);
|
||||
|
||||
if (!reverse) {
|
||||
if (elementName.first.empty()) {
|
||||
shadow.second.swap(elementName.second);
|
||||
return false;
|
||||
}
|
||||
if(shadow==elementName)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool missing = GeoFeature::hasMissingElement(elementName.second.c_str());
|
||||
if (feature == geo && (missing || reverse)) {
|
||||
// If the referenced element is missing, or we are generating element
|
||||
// map for the first time, or we are re-generating the element map due
|
||||
// to version change, i.e. 'reverse', try search by geometry first
|
||||
const char *oldElement = Data::ComplexGeoData::findElementName(shadow.second.c_str());
|
||||
if(!Data::ComplexGeoData::hasMissingElement(oldElement)) {
|
||||
const auto &names = geo->searchElementCache(oldElement);
|
||||
if(names.size()) {
|
||||
missing = false;
|
||||
std::string newsub(subname, strlen(subname) - strlen(element));
|
||||
newsub += names.front();
|
||||
GeoFeature::resolveElement(obj, newsub.c_str(), elementName,true,
|
||||
GeoFeature::ElementNameType::Export,feature);
|
||||
FC_WARN(propertyName(this)
|
||||
<< " auto change element reference " << ret->getFullName() << " "
|
||||
<< (shadow.first.size()?shadow.first:shadow.second) << " -> "
|
||||
<< (elementName.first.size()?elementName.first:elementName.second));
|
||||
}
|
||||
// Note: the following code proves to be too risky. There is no way
|
||||
// (so far) to ensure the recompute do not change the geometry. If
|
||||
// the geometry does remain the same, the above geometry search
|
||||
// should be able to find the new reference any way!
|
||||
#if 0
|
||||
else if (missing && reverse && shadow.first.size()) {
|
||||
// reverse means we are trying to either generate the element
|
||||
// name for the first time, or upgrade to a new map version. In
|
||||
// case of upgrading, we still consult the original mapped name
|
||||
// in first try. Here means the first try failed, and the
|
||||
// geometry search cannot find any match, so we try the
|
||||
// non-mapped name as a last resort.
|
||||
//
|
||||
// WARNING! We are assuming the recomputation is done with no
|
||||
// actual property change, and the resulting geometry remains
|
||||
// the same. If this condition is not met, the result may be
|
||||
// undesirable. TODO: find a way to ensure this condition.
|
||||
|
||||
GeoFeature::resolveElement(obj, shadow.second.c_str(), elementName, true,
|
||||
GeoFeature::ElementNameType::Export,feature);
|
||||
if(!elementName.second.empty()) {
|
||||
missing = Data::ComplexGeoData::hasMissingElement(elementName.second.c_str());
|
||||
if (!missing) {
|
||||
FC_WARN(propertyName(this)
|
||||
<< " element reference changed " << ret->getFullName() << " "
|
||||
<< shadow.first << " -> " << elementName.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if(notify)
|
||||
aboutToSetValue();
|
||||
if(missing) {
|
||||
FC_WARN(propertyName(this)
|
||||
<< " missing element reference " << ret->getFullName() << " "
|
||||
<< (elementName.first.size()?elementName.first:elementName.second));
|
||||
shadow.second.swap(elementName.second);
|
||||
} else {
|
||||
FC_TRACE(propertyName(this)
|
||||
<< " element reference shadow update " << ret->getFullName() << " "
|
||||
<< shadow.first << " -> " << elementName.first);
|
||||
shadow.swap(elementName);
|
||||
if(shadow.first.size() && Data::ComplexGeoData::hasMappedElementName(sub.c_str()))
|
||||
sub = shadow.first;
|
||||
}
|
||||
|
||||
if(reverse) {
|
||||
if(shadow.first.size() && Data::ComplexGeoData::hasMappedElementName(sub.c_str()))
|
||||
sub = shadow.first;
|
||||
else
|
||||
sub = shadow.second;
|
||||
return true;
|
||||
}
|
||||
if (missing) {
|
||||
if (sub != shadow.first)
|
||||
sub = shadow.second;
|
||||
return true;
|
||||
}
|
||||
auto pos2 = shadow.first.rfind('.');
|
||||
if(pos2 == std::string::npos)
|
||||
return true;
|
||||
++pos2;
|
||||
auto pos = sub.rfind('.');
|
||||
if(pos == std::string::npos)
|
||||
pos = 0;
|
||||
else
|
||||
++pos;
|
||||
if(pos==pos2) {
|
||||
if(sub.compare(pos,sub.size()-pos,&shadow.first[pos2])!=0) {
|
||||
FC_LOG("element reference update " << sub << " -> " << shadow.first);
|
||||
sub.replace(pos,sub.size()-pos,&shadow.first[pos2]);
|
||||
}
|
||||
} else if(sub!=shadow.second) {
|
||||
FC_LOG("element reference update " << sub << " -> " << shadow.second);
|
||||
sub = shadow.second;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::pair<DocumentObject*, std::string>
|
||||
@@ -290,6 +475,8 @@ PropertyLinkBase::tryReplaceLink(const PropertyContainer *owner, DocumentObject
|
||||
{
|
||||
std::pair<DocumentObject*, std::string> res;
|
||||
res.first = 0;
|
||||
if (!obj)
|
||||
return res;
|
||||
|
||||
if(oldObj == obj) {
|
||||
if(owner == parent) {
|
||||
@@ -309,6 +496,11 @@ PropertyLinkBase::tryReplaceLink(const PropertyContainer *owner, DocumentObject
|
||||
return res;
|
||||
}
|
||||
return res;
|
||||
} else if (newObj == obj) {
|
||||
// This means the new object is already sub-object of this parent
|
||||
// (consider a case of swapping the tool and base object of the Cut
|
||||
// feature). We'll swap the old and new object.
|
||||
return tryReplaceLink(owner, obj, parent, newObj, oldObj, subname);
|
||||
}
|
||||
if(!subname || !subname[0])
|
||||
return res;
|
||||
@@ -319,6 +511,8 @@ PropertyLinkBase::tryReplaceLink(const PropertyContainer *owner, DocumentObject
|
||||
for(auto pos=sub.find('.');pos!=std::string::npos;pos=sub.find('.',pos)) {
|
||||
++pos;
|
||||
char c = sub[pos];
|
||||
if (c == '.')
|
||||
continue;
|
||||
sub[pos] = 0;
|
||||
auto sobj = obj->getSubObject(sub.c_str());
|
||||
sub[pos] = c;
|
||||
@@ -343,6 +537,8 @@ PropertyLinkBase::tryReplaceLink(const PropertyContainer *owner, DocumentObject
|
||||
return res;
|
||||
}
|
||||
break;
|
||||
}else if(sobj == newObj) {
|
||||
return tryReplaceLink(owner, obj, parent, newObj, oldObj, subname);
|
||||
}else if(prev == parent)
|
||||
break;
|
||||
prev = sobj;
|
||||
@@ -358,6 +554,8 @@ PropertyLinkBase::tryReplaceLinkSubs(const PropertyContainer *owner,
|
||||
{
|
||||
std::pair<DocumentObject*,std::vector<std::string> > res;
|
||||
res.first = 0;
|
||||
if (!obj)
|
||||
return res;
|
||||
|
||||
auto r = tryReplaceLink(owner,obj,parent,oldObj,newObj);
|
||||
if(r.first) {
|
||||
@@ -917,7 +1115,7 @@ TYPESYSTEM_SOURCE(App::PropertyLinkSubHidden, App::PropertyLinkSub)
|
||||
|
||||
|
||||
PropertyLinkSub::PropertyLinkSub()
|
||||
: _pcLinkSub(nullptr), _restoreLabel(false)
|
||||
: _pcLinkSub(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
@@ -993,13 +1191,22 @@ const std::vector<std::string>& PropertyLinkSub::getSubValues(void) const
|
||||
}
|
||||
|
||||
static inline const std::string &getSubNameWithStyle(const std::string &subName,
|
||||
const PropertyLinkBase::ShadowSub &shadow, bool newStyle)
|
||||
const PropertyLinkBase::ShadowSub &shadow, bool newStyle, std::string &tmp)
|
||||
{
|
||||
if(!newStyle) {
|
||||
if(shadow.second.size())
|
||||
return shadow.second;
|
||||
}else if(shadow.first.size())
|
||||
}else if(shadow.first.size()) {
|
||||
if (Data::ComplexGeoData::hasMissingElement(shadow.second.c_str())) {
|
||||
auto pos = shadow.first.rfind('.');
|
||||
if (pos != std::string::npos) {
|
||||
tmp = shadow.first.substr(0, pos+1);
|
||||
tmp += shadow.second;
|
||||
return tmp;
|
||||
}
|
||||
}
|
||||
return shadow.first;
|
||||
}
|
||||
return subName;
|
||||
}
|
||||
|
||||
@@ -1007,20 +1214,24 @@ std::vector<std::string> PropertyLinkSub::getSubValues(bool newStyle) const {
|
||||
assert(_cSubList.size() == _ShadowSubList.size());
|
||||
std::vector<std::string> ret;
|
||||
ret.reserve(_cSubList.size());
|
||||
std::string tmp;
|
||||
for(size_t i=0;i<_ShadowSubList.size();++i)
|
||||
ret.push_back(getSubNameWithStyle(_cSubList[i],_ShadowSubList[i],newStyle));
|
||||
ret.push_back(getSubNameWithStyle(_cSubList[i],_ShadowSubList[i],newStyle,tmp));
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<std::string> PropertyLinkSub::getSubValuesStartsWith(const char* starter, bool newStyle) const
|
||||
{
|
||||
(void)newStyle;
|
||||
|
||||
std::vector<std::string> temp;
|
||||
for(std::vector<std::string>::const_iterator it=_cSubList.begin();it!=_cSubList.end();++it)
|
||||
if(strncmp(starter,it->c_str(),strlen(starter))==0)
|
||||
temp.push_back(*it);
|
||||
return temp;
|
||||
assert(_cSubList.size() == _ShadowSubList.size());
|
||||
std::vector<std::string> ret;
|
||||
std::string tmp;
|
||||
for(size_t i=0;i<_ShadowSubList.size();++i) {
|
||||
const auto &sub = getSubNameWithStyle(_cSubList[i],_ShadowSubList[i],newStyle,tmp);
|
||||
auto element = Data::ComplexGeoData::findElementName(sub.c_str());
|
||||
if(element && boost::starts_with(element,starter))
|
||||
ret.emplace_back(element);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
App::DocumentObject * PropertyLinkSub::getValue(Base::Type t) const
|
||||
@@ -1035,8 +1246,9 @@ PyObject *PropertyLinkSub::getPyObject(void)
|
||||
if (_pcLinkSub) {
|
||||
_pcLinkSub->getPyObject();
|
||||
tup[0] = Py::asObject(_pcLinkSub->getPyObject());
|
||||
for(unsigned int i = 0;i<_cSubList.size(); i++)
|
||||
list[i] = Py::String(_cSubList[i]);
|
||||
int i = 0;
|
||||
for (auto &sub : getSubValues(testFlag(LinkNewElement)))
|
||||
list[i++] = Py::String(sub);
|
||||
tup[1] = list;
|
||||
return Py::new_reference_to(tup);
|
||||
}
|
||||
@@ -1309,12 +1521,9 @@ std::string PropertyLinkBase::tryImportSubName(const App::DocumentObject *obj, c
|
||||
return std::string();
|
||||
}
|
||||
|
||||
#define ATTR_SHADOWED "shadowed"
|
||||
#define ATTR_SHADOW "shadow"
|
||||
#define ATTR_MAPPED "mapped"
|
||||
|
||||
// We do not have topo naming yet, ignore shadow sub for now
|
||||
#define IGNORE_SHADOW true
|
||||
static const char *AttrShadowed = "shadowed";
|
||||
static const char *AttrShadow = "shadow";
|
||||
static const char *AttrMapped = "mapped";
|
||||
|
||||
void PropertyLinkSub::Save (Base::Writer &writer) const
|
||||
{
|
||||
@@ -1327,7 +1536,7 @@ void PropertyLinkSub::Save (Base::Writer &writer) const
|
||||
internal_name = _pcLinkSub->getExportName();
|
||||
writer.Stream() << writer.ind() << "<LinkSub value=\""
|
||||
<< internal_name <<"\" count=\"" << _cSubList.size();
|
||||
writer.Stream() << "\">" << std::endl;
|
||||
writer.Stream() << "\">\n";
|
||||
writer.incInd();
|
||||
auto owner = dynamic_cast<DocumentObject*>(getContainer());
|
||||
bool exporting = owner && owner->isExporting();
|
||||
@@ -1342,18 +1551,18 @@ void PropertyLinkSub::Save (Base::Writer &writer) const
|
||||
std::string exportName;
|
||||
writer.Stream() << encodeAttribute(exportSubName(exportName,_pcLinkSub,sub.c_str()));
|
||||
if(shadow.second.size() && shadow.first == _cSubList[i])
|
||||
writer.Stream() << "\" " ATTR_MAPPED "=\"1";
|
||||
writer.Stream() << "\" " << AttrMapped << "=\"1";
|
||||
} else {
|
||||
writer.Stream() << encodeAttribute(sub);
|
||||
if(_cSubList[i].size()) {
|
||||
if(sub!=_cSubList[i]) {
|
||||
// Stores the actual value that is shadowed. For new version FC,
|
||||
// we will restore this shadowed value instead.
|
||||
writer.Stream() << "\" " ATTR_SHADOWED "=\"" << encodeAttribute(_cSubList[i]);
|
||||
writer.Stream() << "\" " << AttrShadowed << "=\"" << encodeAttribute(_cSubList[i]);
|
||||
}else if(shadow.first.size()){
|
||||
// Here means the user set value is old style element name.
|
||||
// We shall then store the shadow somewhere else.
|
||||
writer.Stream() << "\" " ATTR_SHADOW "=\"" << encodeAttribute(shadow.first);
|
||||
writer.Stream() << "\" " << AttrShadow << "=\"" << encodeAttribute(shadow.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1394,15 +1603,15 @@ void PropertyLinkSub::Restore(Base::XMLReader &reader)
|
||||
for (int i = 0; i < count; i++) {
|
||||
reader.readElement("Sub");
|
||||
shadows[i].second = importSubName(reader,reader.getAttribute("value"),restoreLabel);
|
||||
if(reader.hasAttribute(ATTR_SHADOWED) && !IGNORE_SHADOW) {
|
||||
if(reader.hasAttribute(AttrShadowed)) {
|
||||
values[i] = shadows[i].first =
|
||||
importSubName(reader,reader.getAttribute(ATTR_SHADOWED),restoreLabel);
|
||||
importSubName(reader,reader.getAttribute(AttrShadowed),restoreLabel);
|
||||
} else {
|
||||
values[i] = shadows[i].second;
|
||||
if(reader.hasAttribute(ATTR_SHADOW) && !IGNORE_SHADOW)
|
||||
shadows[i].first = importSubName(reader,reader.getAttribute(ATTR_SHADOW),restoreLabel);
|
||||
if(reader.hasAttribute(AttrShadow))
|
||||
shadows[i].first = importSubName(reader,reader.getAttribute(AttrShadow),restoreLabel);
|
||||
}
|
||||
if(reader.hasAttribute(ATTR_MAPPED))
|
||||
if(reader.hasAttribute(AttrMapped))
|
||||
mapped.push_back(i);
|
||||
}
|
||||
setFlag(LinkRestoreLabel,restoreLabel);
|
||||
@@ -1502,6 +1711,7 @@ Property *PropertyLinkSub::Copy(void) const
|
||||
PropertyLinkSub *p= new PropertyLinkSub();
|
||||
p->_pcLinkSub = _pcLinkSub;
|
||||
p->_cSubList = _cSubList;
|
||||
p->_ShadowSubList = _ShadowSubList;
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -1999,7 +2209,7 @@ std::vector<PropertyLinkSubList::SubSet> PropertyLinkSubList::getSubListValues(b
|
||||
PyObject *PropertyLinkSubList::getPyObject(void)
|
||||
{
|
||||
#if 1
|
||||
std::vector<SubSet> subLists = getSubListValues();
|
||||
std::vector<SubSet> subLists = getSubListValues(testFlag(LinkNewElement));
|
||||
std::size_t count = subLists.size();
|
||||
#if 0//FIXME: Should switch to tuple
|
||||
Py::Tuple sequence(count);
|
||||
@@ -2190,18 +2400,18 @@ void PropertyLinkSubList::Save (Base::Writer &writer) const
|
||||
std::string exportName;
|
||||
writer.Stream() << encodeAttribute(exportSubName(exportName,obj,sub.c_str()));
|
||||
if(shadow.second.size() && _lSubList[i]==shadow.first)
|
||||
writer.Stream() << "\" " ATTR_MAPPED "=\"1";
|
||||
writer.Stream() << "\" " << AttrMapped << "=\"1";
|
||||
} else {
|
||||
writer.Stream() << encodeAttribute(sub);
|
||||
if(_lSubList[i].size()) {
|
||||
if(sub!=_lSubList[i]) {
|
||||
// Stores the actual value that is shadowed. For new version FC,
|
||||
// we will restore this shadowed value instead.
|
||||
writer.Stream() << "\" " ATTR_SHADOWED "=\"" << encodeAttribute(_lSubList[i]);
|
||||
writer.Stream() << "\" " << AttrShadowed << "=\"" << encodeAttribute(_lSubList[i]);
|
||||
}else if(shadow.first.size()) {
|
||||
// Here means the user set value is old style element name.
|
||||
// We shall then store the shadow somewhere else.
|
||||
writer.Stream() << "\" " ATTR_SHADOW "=\"" << encodeAttribute(shadow.first);
|
||||
writer.Stream() << "\" " << AttrShadow << "=\"" << encodeAttribute(shadow.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2242,15 +2452,15 @@ void PropertyLinkSubList::Restore(Base::XMLReader &reader)
|
||||
shadows.emplace_back();
|
||||
auto &shadow = shadows.back();
|
||||
shadow.second = importSubName(reader,reader.getAttribute("sub"),restoreLabel);
|
||||
if(reader.hasAttribute(ATTR_SHADOWED) && !IGNORE_SHADOW) {
|
||||
shadow.first = importSubName(reader,reader.getAttribute(ATTR_SHADOWED),restoreLabel);
|
||||
if(reader.hasAttribute(AttrShadowed)) {
|
||||
shadow.first = importSubName(reader,reader.getAttribute(AttrShadowed),restoreLabel);
|
||||
SubNames.push_back(shadow.first);
|
||||
}else{
|
||||
SubNames.push_back(shadow.second);
|
||||
if(reader.hasAttribute(ATTR_SHADOW) && !IGNORE_SHADOW)
|
||||
shadow.first = importSubName(reader,reader.getAttribute(ATTR_SHADOW),restoreLabel);
|
||||
if(reader.hasAttribute(AttrShadow))
|
||||
shadow.first = importSubName(reader,reader.getAttribute(AttrShadow),restoreLabel);
|
||||
}
|
||||
if(reader.hasAttribute(ATTR_MAPPED))
|
||||
if(reader.hasAttribute(AttrMapped))
|
||||
mapped.push_back(i);
|
||||
} else if (reader.isVerbose())
|
||||
Base::Console().Warning("Lost link to '%s' while loading, maybe "
|
||||
@@ -2261,7 +2471,7 @@ void PropertyLinkSubList::Restore(Base::XMLReader &reader)
|
||||
reader.readEndElement("LinkSubList");
|
||||
|
||||
// assignment
|
||||
setValues(values,SubNames,std::move(shadows));
|
||||
setValues(values,std::move(SubNames),std::move(shadows));
|
||||
_mapped.swap(mapped);
|
||||
}
|
||||
|
||||
@@ -2450,6 +2660,7 @@ Property *PropertyLinkSubList::Copy(void) const
|
||||
PropertyLinkSubList *p = new PropertyLinkSubList();
|
||||
p->_lValueList = _lValueList;
|
||||
p->_lSubList = _lSubList;
|
||||
p->_ShadowSubList = _ShadowSubList;
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -2473,8 +2684,9 @@ std::vector<std::string> PropertyLinkSubList::getSubValues(bool newStyle) const
|
||||
assert(_lSubList.size() == _ShadowSubList.size());
|
||||
std::vector<std::string> ret;
|
||||
ret.reserve(_ShadowSubList.size());
|
||||
std::string tmp;
|
||||
for(size_t i=0;i<_ShadowSubList.size();++i)
|
||||
ret.push_back(getSubNameWithStyle(_lSubList[i],_ShadowSubList[i],newStyle));
|
||||
ret.push_back(getSubNameWithStyle(_lSubList[i],_ShadowSubList[i],newStyle,tmp));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -2627,7 +2839,7 @@ public:
|
||||
App::Document *pDoc,PropertyXLink *l, const char *objName)
|
||||
{
|
||||
QString path;
|
||||
l->filePath = getDocPath(filename,pDoc,true,&path);
|
||||
l->filePath = getDocPath(filename,pDoc,false,&path);
|
||||
|
||||
FC_LOG("finding doc " << filename);
|
||||
|
||||
@@ -2978,6 +3190,7 @@ PropertyXLink::PropertyXLink(bool _allowPartial, PropertyLinkBase *parent)
|
||||
setAllowPartial(_allowPartial);
|
||||
setAllowExternal(true);
|
||||
setSyncSubObject(true);
|
||||
// setReturnNewElement(true);
|
||||
if(parent)
|
||||
setContainer(parent->getContainer());
|
||||
}
|
||||
@@ -3009,18 +3222,27 @@ void PropertyXLink::detach() {
|
||||
}
|
||||
}
|
||||
|
||||
std::string PropertyXLink::getFullName(bool python) const {
|
||||
if(getName() || python || !parentProp)
|
||||
return inherited::getFullName(python);
|
||||
|
||||
std::ostringstream ss;
|
||||
ss << parentProp->getFullName() << ":" << this;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void PropertyXLink::aboutToSetValue() {
|
||||
if(parentProp)
|
||||
parentProp->aboutToSetChildValue(*this);
|
||||
else
|
||||
PropertyLinkBase::aboutToSetValue();
|
||||
inherited::aboutToSetValue();
|
||||
}
|
||||
|
||||
void PropertyXLink::hasSetValue() {
|
||||
if(parentProp)
|
||||
parentProp->hasSetChildValue(*this);
|
||||
else
|
||||
PropertyLinkBase::hasSetValue();
|
||||
inherited::hasSetValue();
|
||||
}
|
||||
|
||||
void PropertyXLink::setSubName(const char *subname)
|
||||
@@ -3329,9 +3551,11 @@ void PropertyXLink::Save (Base::Writer &writer) const {
|
||||
}else
|
||||
FC_WARN("PropertyXLink export without saving the document");
|
||||
}
|
||||
if(_path.size())
|
||||
path = _path.c_str();
|
||||
}
|
||||
} else if (filePath.size())
|
||||
_path = DocInfo::getDocPath(filePath.c_str(),owner->getDocument(),true);
|
||||
|
||||
if(_path.size())
|
||||
path = _path.c_str();
|
||||
writer.Stream() << writer.ind()
|
||||
<< "<XLink file=\"" << encodeAttribute(path)
|
||||
<< "\" stamp=\"" << (docInfo&&docInfo->pcDoc?docInfo->pcDoc->LastModifiedDate.getValue():"")
|
||||
@@ -3352,14 +3576,14 @@ void PropertyXLink::Save (Base::Writer &writer) const {
|
||||
writer.Stream() << "\" sub=\"" <<
|
||||
encodeAttribute(exportSubName(exportName,_pcLink,sub.c_str()));
|
||||
if(shadowSub.second.size() && shadowSub.first==subName)
|
||||
writer.Stream() << "\" " ATTR_MAPPED "=\"1";
|
||||
writer.Stream() << "\" " << AttrMapped << "=\"1";
|
||||
}else{
|
||||
writer.Stream() << "\" sub=\"" << encodeAttribute(sub);
|
||||
if(sub.size()) {
|
||||
if(sub!=subName)
|
||||
writer.Stream() << "\" " ATTR_SHADOWED "=\"" << encodeAttribute(subName);
|
||||
writer.Stream() << "\" " << AttrShadowed << "=\"" << encodeAttribute(subName);
|
||||
else if(shadowSub.first.size())
|
||||
writer.Stream() << "\" " ATTR_SHADOW "=\"" << encodeAttribute(shadowSub.first);
|
||||
writer.Stream() << "\" " << AttrShadow << "=\"" << encodeAttribute(shadowSub.first);
|
||||
}
|
||||
}
|
||||
writer.Stream() << "\"/>" << std::endl;
|
||||
@@ -3377,14 +3601,14 @@ void PropertyXLink::Save (Base::Writer &writer) const {
|
||||
std::string exportName;
|
||||
writer.Stream() << encodeAttribute(exportSubName(exportName,_pcLink,sub.c_str()));
|
||||
if(shadow.second.size() && shadow.first == _SubList[i])
|
||||
writer.Stream() << "\" " ATTR_MAPPED "=\"1";
|
||||
writer.Stream() << "\" " << AttrMapped << "=\"1";
|
||||
} else {
|
||||
writer.Stream() << encodeAttribute(sub);
|
||||
if(_SubList[i].size()) {
|
||||
if(sub!=_SubList[i])
|
||||
writer.Stream() << "\" " ATTR_SHADOWED "=\"" << encodeAttribute(_SubList[i]);
|
||||
writer.Stream() << "\" " << AttrShadowed << "=\"" << encodeAttribute(_SubList[i]);
|
||||
else if(shadow.first.size())
|
||||
writer.Stream() << "\" " ATTR_SHADOW "=\"" << encodeAttribute(shadow.first);
|
||||
writer.Stream() << "\" " << AttrShadow << "=\"" << encodeAttribute(shadow.first);
|
||||
}
|
||||
}
|
||||
writer.Stream()<<"\"/>" << endl;
|
||||
@@ -3431,19 +3655,19 @@ void PropertyXLink::Restore(Base::XMLReader &reader)
|
||||
std::vector<int> mapped;
|
||||
bool restoreLabel = false;
|
||||
if(reader.hasAttribute("sub")) {
|
||||
if(reader.hasAttribute(ATTR_MAPPED))
|
||||
if(reader.hasAttribute(AttrMapped))
|
||||
mapped.push_back(0);
|
||||
subs.emplace_back();
|
||||
auto &subname = subs.back();
|
||||
shadows.emplace_back();
|
||||
auto &shadow = shadows.back();
|
||||
shadow.second = importSubName(reader,reader.getAttribute("sub"),restoreLabel);
|
||||
if(reader.hasAttribute(ATTR_SHADOWED) && !IGNORE_SHADOW)
|
||||
subname = shadow.first = importSubName(reader,reader.getAttribute(ATTR_SHADOWED),restoreLabel);
|
||||
if(reader.hasAttribute(AttrShadowed))
|
||||
subname = shadow.first = importSubName(reader,reader.getAttribute(AttrShadowed),restoreLabel);
|
||||
else {
|
||||
subname = shadow.second;
|
||||
if(reader.hasAttribute(ATTR_SHADOW) && !IGNORE_SHADOW)
|
||||
shadow.first = importSubName(reader,reader.getAttribute(ATTR_SHADOW),restoreLabel);
|
||||
if(reader.hasAttribute(AttrShadow))
|
||||
shadow.first = importSubName(reader,reader.getAttribute(AttrShadow),restoreLabel);
|
||||
}
|
||||
}else if(reader.hasAttribute("count")) {
|
||||
int count = reader.getAttributeAsInteger("count");
|
||||
@@ -3452,15 +3676,15 @@ void PropertyXLink::Restore(Base::XMLReader &reader)
|
||||
for (int i = 0; i < count; i++) {
|
||||
reader.readElement("Sub");
|
||||
shadows[i].second = importSubName(reader,reader.getAttribute("value"),restoreLabel);
|
||||
if(reader.hasAttribute(ATTR_SHADOWED) && !IGNORE_SHADOW)
|
||||
subs[i] = shadows[i].first =
|
||||
importSubName(reader,reader.getAttribute(ATTR_SHADOWED),restoreLabel);
|
||||
if(reader.hasAttribute(AttrShadowed))
|
||||
subs[i] = shadows[i].first =
|
||||
importSubName(reader,reader.getAttribute(AttrShadowed),restoreLabel);
|
||||
else {
|
||||
subs[i] = shadows[i].second;
|
||||
if(reader.hasAttribute(ATTR_SHADOW) && !IGNORE_SHADOW)
|
||||
shadows[i].first = importSubName(reader,reader.getAttribute(ATTR_SHADOW),restoreLabel);
|
||||
if(reader.hasAttribute(AttrShadow))
|
||||
shadows[i].first = importSubName(reader,reader.getAttribute(AttrShadow),restoreLabel);
|
||||
}
|
||||
if(reader.hasAttribute(ATTR_MAPPED))
|
||||
if(reader.hasAttribute(AttrMapped))
|
||||
mapped.push_back(i);
|
||||
}
|
||||
reader.readEndElement("XLink");
|
||||
@@ -3541,8 +3765,10 @@ void PropertyXLink::copyTo(PropertyXLink &other,
|
||||
}
|
||||
if(subs)
|
||||
other._SubList = std::move(*subs);
|
||||
else
|
||||
else {
|
||||
other._SubList = _SubList;
|
||||
other._ShadowSubList = _ShadowSubList;
|
||||
}
|
||||
other._Flags = _Flags;
|
||||
}
|
||||
|
||||
@@ -3661,7 +3887,7 @@ PyObject *PropertyXLink::getPyObject(void)
|
||||
{
|
||||
if(!_pcLink)
|
||||
Py_Return;
|
||||
const auto &subs = getSubValues(false);
|
||||
const auto &subs = getSubValues(testFlag(LinkNewElement));
|
||||
if(subs.empty())
|
||||
return _pcLink->getPyObject();
|
||||
Py::Tuple ret(2);
|
||||
@@ -3725,7 +3951,7 @@ void PropertyXLink::setPyObject(PyObject *value) {
|
||||
const char *PropertyXLink::getSubName(bool newStyle) const {
|
||||
if(_SubList.empty() || _ShadowSubList.empty())
|
||||
return "";
|
||||
return getSubNameWithStyle(_SubList[0],_ShadowSubList[0],newStyle).c_str();
|
||||
return getSubNameWithStyle(_SubList[0],_ShadowSubList[0],newStyle,tmpShadow).c_str();
|
||||
}
|
||||
|
||||
void PropertyXLink::getLinks(std::vector<App::DocumentObject *> &objs,
|
||||
@@ -3756,20 +3982,24 @@ std::vector<std::string> PropertyXLink::getSubValues(bool newStyle) const {
|
||||
assert(_SubList.size() == _ShadowSubList.size());
|
||||
std::vector<std::string> ret;
|
||||
ret.reserve(_SubList.size());
|
||||
std::string tmp;
|
||||
for(size_t i=0;i<_ShadowSubList.size();++i)
|
||||
ret.push_back(getSubNameWithStyle(_SubList[i],_ShadowSubList[i],newStyle));
|
||||
ret.push_back(getSubNameWithStyle(_SubList[i],_ShadowSubList[i],newStyle,tmp));
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<std::string> PropertyXLink::getSubValuesStartsWith(const char* starter, bool newStyle) const
|
||||
{
|
||||
(void)newStyle;
|
||||
|
||||
std::vector<std::string> temp;
|
||||
for(std::vector<std::string>::const_iterator it=_SubList.begin();it!=_SubList.end();++it)
|
||||
if(strncmp(starter,it->c_str(),strlen(starter))==0)
|
||||
temp.push_back(*it);
|
||||
return temp;
|
||||
assert(_SubList.size() == _ShadowSubList.size());
|
||||
std::vector<std::string> ret;
|
||||
std::string tmp;
|
||||
for(size_t i=0;i<_ShadowSubList.size();++i) {
|
||||
const auto &sub = getSubNameWithStyle(_SubList[i],_ShadowSubList[i],newStyle,tmp);
|
||||
auto element = Data::ComplexGeoData::findElementName(sub.c_str());
|
||||
if(element && boost::starts_with(element,starter))
|
||||
ret.emplace_back(element);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void PropertyXLink::setAllowPartial(bool enable) {
|
||||
@@ -3825,7 +4055,7 @@ PyObject *PropertyXLinkSub::getPyObject(void)
|
||||
Py_Return;
|
||||
Py::Tuple ret(2);
|
||||
ret.setItem(0,Py::Object(_pcLink->getPyObject(),true));
|
||||
const auto &subs = getSubValues(false);
|
||||
const auto &subs = getSubValues(testFlag(LinkNewElement));
|
||||
Py::List list(subs.size());
|
||||
int i = 0;
|
||||
PropertyString propString;
|
||||
@@ -3851,6 +4081,7 @@ PropertyXLinkSubList::PropertyXLinkSubList()
|
||||
{
|
||||
_pcScope = LinkScope::Global;
|
||||
setSyncSubObject(true);
|
||||
// setReturnNewElement(true);
|
||||
}
|
||||
|
||||
PropertyXLinkSubList::~PropertyXLinkSubList()
|
||||
@@ -4086,7 +4317,7 @@ PyObject *PropertyXLinkSubList::getPyObject(void)
|
||||
Py::Tuple tup(2);
|
||||
tup[0] = Py::asObject(obj->getPyObject());
|
||||
|
||||
const auto &subs = link.getSubValues();
|
||||
const auto &subs = link.getSubValues(testFlag(LinkNewElement));
|
||||
Py::Tuple items(subs.size());
|
||||
for (std::size_t j = 0; j < subs.size(); j++) {
|
||||
items[j] = Py::String(subs[j]);
|
||||
@@ -4106,7 +4337,7 @@ void PropertyXLinkSubList::setPyObject(PyObject *value)
|
||||
this->setValue(dummy.getValue(), dummy.getSubValues());
|
||||
return;
|
||||
}
|
||||
catch (Base::Exception&) {}
|
||||
catch (Base::TypeError&) {}
|
||||
|
||||
if (!PyTuple_Check(value) && !PyList_Check(value))
|
||||
throw Base::TypeError("Invalid type. Accepts (DocumentObject, (subname...)) or sequence of such type.");
|
||||
|
||||
+19
-4
@@ -26,8 +26,11 @@
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include "Property.h"
|
||||
|
||||
namespace Base {
|
||||
@@ -38,6 +41,8 @@ namespace App
|
||||
{
|
||||
class DocumentObject;
|
||||
class Document;
|
||||
class GeoFeature;
|
||||
class SubObjectT;
|
||||
|
||||
class DocInfo;
|
||||
typedef std::shared_ptr<DocInfo> DocInfoPtr;
|
||||
@@ -254,7 +259,7 @@ public:
|
||||
|
||||
/// Helper function to return linked objects using an std::inserter
|
||||
template<class T>
|
||||
void getLinkedObjects(T &inserter, bool all=false) const {
|
||||
void getLinkedObjects(T inserter, bool all=false) const {
|
||||
std::vector<App::DocumentObject*> ret;
|
||||
getLinks(ret,all);
|
||||
std::copy(ret.begin(),ret.end(),inserter);
|
||||
@@ -262,7 +267,7 @@ public:
|
||||
|
||||
/// Helper function to return a map of linked object and its subname references
|
||||
void getLinkedElements(std::map<App::DocumentObject*, std::vector<std::string> > &elements,
|
||||
bool newStyle=true, bool all=true) const
|
||||
bool newStyle=true, bool all=false) const
|
||||
{
|
||||
std::vector<App::DocumentObject*> ret;
|
||||
std::vector<std::string> subs;
|
||||
@@ -275,12 +280,14 @@ public:
|
||||
|
||||
/// Helper function to return a map of linked object and its subname references
|
||||
std::map<App::DocumentObject*, std::vector<std::string> >
|
||||
linkedElements(bool newStyle=true, bool all=true) const
|
||||
linkedElements(bool newStyle=true, bool all=false) const
|
||||
{
|
||||
std::map<App::DocumentObject*, std::vector<std::string> > ret;
|
||||
getLinkedElements(ret,newStyle,all);
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<App::SubObjectT> linkedElementsT(bool all) const;
|
||||
//@}
|
||||
|
||||
virtual bool isSame(const Property &other) const override;
|
||||
@@ -346,6 +353,8 @@ public:
|
||||
/// Update all element references in all link properties of \a feature
|
||||
static void updateElementReferences(DocumentObject *feature, bool reverse=false);
|
||||
|
||||
/// Obtain link properties that contain element references to a given object
|
||||
static const std::unordered_set<PropertyLinkBase*>& getElementReferences(DocumentObject *);
|
||||
|
||||
/** Helper function for update individual element reference
|
||||
*
|
||||
@@ -538,6 +547,7 @@ public:
|
||||
LinkAllowPartial,
|
||||
LinkRestoreLabel,
|
||||
LinkSyncSubObject, // used by DlgPropertyLink
|
||||
LinkNewElement, // return new element name in getPyObject
|
||||
};
|
||||
inline bool testFlag(int flag) const {
|
||||
return _Flags.test((std::size_t)flag);
|
||||
@@ -545,6 +555,8 @@ public:
|
||||
|
||||
virtual void setAllowPartial(bool enable) { (void)enable; }
|
||||
|
||||
void setReturnNewElement(bool enable);
|
||||
|
||||
protected:
|
||||
virtual void hasSetValue() override;
|
||||
|
||||
@@ -1052,6 +1064,7 @@ class PropertyXLinkSubList;
|
||||
class AppExport PropertyXLink : public PropertyLinkGlobal
|
||||
{
|
||||
TYPESYSTEM_HEADER_WITH_OVERRIDE();
|
||||
typedef PropertyLinkGlobal inherited;
|
||||
|
||||
public:
|
||||
PropertyXLink(bool allowPartial=false, PropertyLinkBase *parent=nullptr);
|
||||
@@ -1137,6 +1150,7 @@ public:
|
||||
|
||||
virtual void setAllowPartial(bool enable) override;
|
||||
|
||||
virtual std::string getFullName(bool python=false) const override;
|
||||
const char *getFilePath() const {
|
||||
return filePath.c_str();
|
||||
}
|
||||
@@ -1169,6 +1183,7 @@ protected:
|
||||
std::vector<ShadowSub> _ShadowSubList;
|
||||
std::vector<int> _mapped;
|
||||
PropertyLinkBase *parentProp;
|
||||
mutable std::string tmpShadow;
|
||||
};
|
||||
|
||||
|
||||
@@ -1198,10 +1213,10 @@ class AppExport PropertyXLinkSubList: public PropertyLinkBase
|
||||
{
|
||||
TYPESYSTEM_HEADER_WITH_OVERRIDE();
|
||||
|
||||
public:
|
||||
typedef typename AtomicPropertyChangeInterface<PropertyXLinkSubList>::AtomicPropertyChange atomic_change;
|
||||
friend atomic_change;
|
||||
|
||||
public:
|
||||
PropertyXLinkSubList();
|
||||
virtual ~PropertyXLinkSubList();
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
#include <App/StringHasher.h>
|
||||
#include <App/StringHasherPy.h>
|
||||
#include <App/StringIDPy.h>
|
||||
#include <App/MappedElement.h>
|
||||
#include <App/DocumentParams.h>
|
||||
|
||||
FC_LOG_LEVEL_INIT("App",true,true)
|
||||
@@ -250,6 +251,99 @@ StringIDRef StringHasher::getID(const QByteArray &data, bool binary, bool hashab
|
||||
return StringIDRef(insert(sid));
|
||||
}
|
||||
|
||||
StringIDRef StringHasher::getID(const Data::MappedName &name,
|
||||
const QVector<StringIDRef> & sids)
|
||||
{
|
||||
StringID d;
|
||||
d._postfix = name.postfixBytes();
|
||||
|
||||
Data::IndexedName indexed;
|
||||
if (!d._postfix.size())
|
||||
indexed = Data::IndexedName(name.dataBytes());
|
||||
if (indexed)
|
||||
d._data = QByteArray::fromRawData(indexed.getType(), strlen(indexed.getType()));
|
||||
else
|
||||
d._data = name.dataBytes();
|
||||
|
||||
auto it = _hashes->left.find(&d);
|
||||
if(it!=_hashes->left.end()) {
|
||||
auto res = StringIDRef(it->first);
|
||||
if (indexed)
|
||||
res._index = indexed.getIndex();
|
||||
return res;
|
||||
}
|
||||
|
||||
if (!indexed && name.isRaw())
|
||||
d._data = QByteArray(name.dataBytes().constData(),
|
||||
name.dataBytes().size());
|
||||
|
||||
StringIDRef postfixRef;
|
||||
if (d._postfix.size() && d._postfix.indexOf("#") < 0) {
|
||||
postfixRef = getID(d._postfix, false, false);
|
||||
postfixRef.toBytes(d._postfix);
|
||||
}
|
||||
|
||||
StringIDRef indexRef;
|
||||
if (indexed)
|
||||
indexRef = getID(d._data, false, false);
|
||||
|
||||
StringIDRef sid(new StringID(lastID()+1,d._data,false,false));
|
||||
StringID & id = *sid._sid;
|
||||
if (d._postfix.size()) {
|
||||
id._flags.set(StringID::Postfixed);
|
||||
id._postfix = d._postfix;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
for (auto & s : sids) {
|
||||
if (s && s._sid->_hasher == this)
|
||||
++count;
|
||||
}
|
||||
|
||||
int extra = (postfixRef ? 1 : 0) + (indexRef ? 1 : 0);
|
||||
if (count == sids.size() && !postfixRef && !indexRef)
|
||||
id._sids = sids;
|
||||
else {
|
||||
id._sids.reserve(count + extra);
|
||||
if (postfixRef) {
|
||||
id._flags.set(StringID::PostfixEncoded);
|
||||
id._sids.push_back(postfixRef);
|
||||
}
|
||||
if (indexRef) {
|
||||
id._flags.set(StringID::Indexed);
|
||||
id._sids.push_back(indexRef);
|
||||
}
|
||||
for (auto &s : sids) {
|
||||
if (s && s._sid->_hasher == this)
|
||||
id._sids.push_back(s);
|
||||
}
|
||||
}
|
||||
if (id._sids.size() > 10) {
|
||||
std::sort(id._sids.begin()+extra, id._sids.end());
|
||||
id._sids.erase(std::unique(id._sids.begin()+extra, id._sids.end()), id._sids.end());
|
||||
}
|
||||
|
||||
if (id._postfix.size() && !indexed) {
|
||||
StringID::IndexID res = StringID::fromString(id._data);
|
||||
if (res.id > 0) {
|
||||
int offset = id.isPostfixEncoded() ? 1 : 0;
|
||||
for (int i=offset; i<id._sids.size();++i) {
|
||||
if (id._sids[i].value() == res.id) {
|
||||
if (i!=offset)
|
||||
std::swap(id._sids[offset], id._sids[i]);
|
||||
if (res.index != 0)
|
||||
id._flags.set(StringID::PrefixIDIndex);
|
||||
else
|
||||
id._flags.set(StringID::PrefixID);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return StringIDRef(insert(sid), indexed.getIndex());
|
||||
}
|
||||
|
||||
StringIDRef StringHasher::getID(long id, int index) const {
|
||||
if(id<=0)
|
||||
return StringIDRef();
|
||||
|
||||
@@ -33,6 +33,10 @@
|
||||
#include <Base/Handle.h>
|
||||
#include <Base/Persistence.h>
|
||||
|
||||
namespace Data{
|
||||
class MappedName;
|
||||
}
|
||||
|
||||
namespace App {
|
||||
|
||||
class StringHasher;
|
||||
@@ -426,6 +430,10 @@ public:
|
||||
/** Map text or binary data to an integer */
|
||||
StringIDRef getID(const QByteArray & data, bool binary, bool hashable=true, bool nocopy=false);
|
||||
|
||||
/** Map geometry element name to an integer */
|
||||
StringIDRef getID(const Data::MappedName & name,
|
||||
const QVector<StringIDRef> & sids);
|
||||
|
||||
/** Obtain the reference counted StringID object from numerical id
|
||||
*
|
||||
* This function exists because the stored string may be one way hashed,
|
||||
|
||||
@@ -26,6 +26,12 @@
|
||||
</Documentation>
|
||||
<Parameter Name="Value" Type="Int"/>
|
||||
</Attribute>
|
||||
<Attribute Name="Related" ReadOnly="true">
|
||||
<Documentation>
|
||||
<UserDocu>Return the related string IDs</UserDocu>
|
||||
</Documentation>
|
||||
<Parameter Name="Related" Type="List"/>
|
||||
</Attribute>
|
||||
<Attribute Name="Data" ReadOnly="true">
|
||||
<Documentation>
|
||||
<UserDocu>Return the data associated with this ID</UserDocu>
|
||||
|
||||
@@ -51,6 +51,13 @@ Py::Int StringIDPy::getValue(void) const {
|
||||
return Py::Int(getStringIDPtr()->value());
|
||||
}
|
||||
|
||||
Py::List StringIDPy::getRelated(void) const {
|
||||
Py::List list;
|
||||
for (auto &id : getStringIDPtr()->relatedIDs())
|
||||
list.append(Py::Long(id.value()));
|
||||
return list;
|
||||
}
|
||||
|
||||
Py::String StringIDPy::getData(void) const {
|
||||
return Py::String(getStringIDPtr()->dataToText(this->_index));
|
||||
}
|
||||
|
||||
@@ -240,6 +240,31 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/** Temporary shorten a sub-object path for more efficient traversal */
|
||||
class StringGuard {
|
||||
public:
|
||||
StringGuard(char *c)
|
||||
:c(c)
|
||||
{
|
||||
v1 = c[0];
|
||||
v2 = c[1];
|
||||
c[0] = '.';
|
||||
c[1] = 0;
|
||||
}
|
||||
~StringGuard()
|
||||
{
|
||||
c[0] = v1;
|
||||
c[1] = v2;
|
||||
}
|
||||
|
||||
char *c;
|
||||
char v1;
|
||||
char v2;
|
||||
};
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
struct BaseExport Tools
|
||||
|
||||
Reference in New Issue
Block a user