Merge branch 'FreeCAD:main' into main

This commit is contained in:
HowThatWorks
2024-05-05 01:07:40 +02:00
committed by GitHub
52 changed files with 1186 additions and 192 deletions
+30
View File
@@ -0,0 +1,30 @@
# FreeCAD Privacy Policy
The FreeCAD application does not collect, transmit, share or use any Personal Data.
FreeCAD is community-developed Free Software. The community does not condone the unauthorized usage of private data, so our software does not gather or send personal data.
The FreeCAD website is mostly static, it does not contain any trackers, neither ours nor third-party. The website uses cookies to remember logged in status, timezone and other
data related to navigating the site.
The website does not contain advertisements.
The software does not contain advertisements or trackers either.
## Caveats
FreeCAD is able to load or save files to/from remote servers (for some protocols and platforms). If you choose to load or save a remote file, your IP or other private data might be shared as part of the normal connection flow for the given protocol. This is out of our control and it is up to you to decide whether you trust a remote host.
The FreeCAD eco system includes user developed workbenches. These workbenches can be installed/updated using the Add-on Manager. The Add-on Manager retrieves workbenches from remote servers across the internet. Add-on workbenches are not checked for malicious content. It is your responsibility to decide whether you trust an add-on workbench.
FreeCAD is meant to manipulate CAD files which may contain metadata. It is your responsibility to verify the metadata contained in your files before you share them with others. These files may contain local directory paths which could reveal user names if the user name forms part of the path - as in “C:\MrsCAD\Documents\myFreeCADFile.FCstd”.
While running and for subsequent runs, FreeCAD uses local persistent storage for logs, configuration files, cache, thumbnails, recently accessed files and other information which may contain private data. This stays on local storage.
When reading the online version of the User Manual within FreeCAD, manual contents is requested through HTTPS connections.
FreeCAD is Free Software and therefore may be packaged by other people, who may include additional software or modify the source code. We do not vouch for these third-party packages and cannot tell you what they contain and what they do regarding your privacy. The official packages are explicitly listed in our download page.
- [based on the GIMP privacy policy](https://www.gimp.org/about/privacy.html)
@@ -73,7 +73,11 @@ macro(CompilerChecksAndSetups)
endif()
else(BUILD_DYNAMIC_LINK_PYTHON)
if(CMAKE_COMPILER_IS_CLANGXX)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--undefined,dynamic_lookup")
if(APPLE)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-undefined,dynamic_lookup")
elseif(UNIX)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--undefined,dynamic_lookup")
endif()
endif()
endif(BUILD_DYNAMIC_LINK_PYTHON)
endif(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX)
+5
View File
@@ -637,6 +637,11 @@ unsigned int ComplexGeoData::getMemSize() const
return 0;
}
std::vector<IndexedName> ComplexGeoData::getHigherElements(const char *, bool) const
{
return {};
}
void ComplexGeoData::setMappedChildElements(const std::vector<Data::ElementMap::MappedChildElements> & children)
{
// DO NOT reset element map if there is one. Because we allow mixing child
+3
View File
@@ -321,6 +321,9 @@ public:
/// 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;
+54 -1
View File
@@ -26,6 +26,7 @@
#include <App/GeoFeaturePy.h>
#include "ComplexGeoData.h"
#include "Document.h"
#include "GeoFeature.h"
#include "GeoFeatureGroupExtension.h"
#include "ElementNamingUtils.h"
@@ -146,12 +147,22 @@ DocumentObject *GeoFeature::resolveElement(DocumentObject *obj, const char *subn
subname = "";
const char *element = Data::findElementName(subname);
if(_element) *_element = element;
#ifdef FC_USE_TNP_FIX
elementName.first.clear();
elementName.second.clear();
auto sobj = obj->getSubObject(std::string(subname, element).c_str());
if(!sobj)
return nullptr;
auto linked = sobj->getLinkedObject(true);
auto geo = Base::freecad_dynamic_cast<GeoFeature>(linked);
#else
auto sobj = obj->getSubObject(subname);
if(!sobj)
return nullptr;
obj = sobj->getLinkedObject(true);
auto geo = dynamic_cast<GeoFeature*>(obj);
if(geoFeature)
#endif
if(geoFeature)
*geoFeature = geo;
if(!obj || (filter && obj!=filter))
return nullptr;
@@ -189,3 +200,45 @@ void GeoFeature::setMaterialAppearance(const App::Material& material)
{
Q_UNUSED(material)
}
#ifdef FC_USE_TNP_FIX
bool GeoFeature::hasMissingElement(const char *subname) {
return Data::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;
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);
}
std::vector<Data::IndexedName>
GeoFeature::getHigherElements(const char *element, bool silent) const
{
auto prop = getPropertyOfGeometry();
if (!prop)
return {};
return prop->getComplexData()->getHigherElements(element, silent);
}
#endif
+14
View File
@@ -140,7 +140,21 @@ public:
* appearance from an App::Material object.
*/
virtual void setMaterialAppearance(const App::Material& material);
#ifdef FC_USE_TNP_FIX
static bool hasMissingElement(const char *subname);
/// Return the object that owns the shape that contains the give element name
virtual DocumentObject *getElementOwner(const Data::MappedName & /*name*/) const
{return nullptr;}
/// Return the higher level element names of the given element
virtual std::vector<Data::IndexedName> getHigherElements(const char *name, bool silent=false) const;
protected:
void onChanged(const Property* prop) override;
// void onDocumentRestored() override;
void updateElementReference();
#endif
protected:
std::pair<std::string, std::string> _getElementName(const char* name,
const Data::MappedElement& mapped) const;
+1 -1
View File
@@ -32,7 +32,7 @@ measureType : Measure.MeasureBasePython
The actual measure type.</UserDocu>
</Documentation>
</Methode>
<Methode Name="getMeasureTypes" Static="true">
<Methode Name="getMeasureTypes" Static="true" NoArgs="true">
<Documentation>
<UserDocu>getMeasureTypes() -> List[(id, label, pythonMeasureType)]
+3 -3
View File
@@ -63,7 +63,7 @@ PyObject* MeasureManagerPy::addMeasureType(PyObject *args)
}
PyObject* MeasureManagerPy::getMeasureTypes(PyObject *args)
PyObject* MeasureManagerPy::getMeasureTypes()
{
Py::List types;
for (auto & it : MeasureManager::getMeasureTypes()) {
@@ -73,7 +73,7 @@ PyObject* MeasureManagerPy::getMeasureTypes(PyObject *args)
type.setItem(2, Py::Object(it->pythonClass));
types.append(type);
}
}
return Py::new_reference_to(types);
}
}
+358 -37
View File
@@ -38,6 +38,8 @@
#include "DocumentObject.h"
#include "DocumentObjectPy.h"
#include "ObjectIdentifier.h"
#include "ElementNamingUtils.h"
#include "GeoFeature.h"
FC_LOG_LEVEL_INIT("PropertyLinks",true,true)
@@ -55,6 +57,9 @@ namespace sp = std::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() = default;
PropertyLinkBase::~PropertyLinkBase() {
@@ -97,6 +102,18 @@ bool PropertyLinkBase::isSame(const Property &other) const
}
void PropertyLinkBase::unregisterElementReference() {
#ifdef FC_USE_TNP_FIX
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();
#endif
}
void PropertyLinkBase::unregisterLabelReferences()
@@ -202,15 +219,71 @@ static std::string propertyName(const Property *prop) {
}
void PropertyLinkBase::updateElementReferences(DocumentObject *feature, bool reverse) {
#ifdef FC_USE_TNP_FIX
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());
}
}
}
#else
(void)feature;
(void)reverse;
#endif
}
void PropertyLinkBase::_registerElementReference(App::DocumentObject *obj, std::string &sub, ShadowSub &shadow)
{
#ifdef FC_USE_TNP_FIX
if (!obj || !obj->getNameInDocument() || sub.empty()) {
return;
}
if (shadow.first.empty()) {
_updateElementReference(0, obj, sub, shadow, false);
return;
}
GeoFeature* geo = nullptr;
const char* element = nullptr;
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);
}
#else
(void)obj;
(void)sub;
(void)shadow;
#endif
}
class StringGuard {
@@ -275,12 +348,157 @@ bool PropertyLinkBase::_updateElementReference(DocumentObject *feature,
App::DocumentObject *obj, std::string &sub, ShadowSub &shadow,
bool reverse, bool notify)
{
#ifdef FC_USE_TNP_FIX
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 = nullptr;
const char* element = nullptr;
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 = Data::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::findElementName(shadow.second.c_str());
if (!Data::hasMissingElement(oldElement)) {
// const auto& names = geo->searchElementCache(oldElement);
std::vector<std::string> names; // searchElementCache isn't implemented.
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);
const auto& oldName = shadow.first.size() ? shadow.first : shadow.second;
const auto& newName =
elementName.first.size() ? elementName.first : elementName.second;
if (oldName != newName) {
FC_WARN(propertyName(this)
<< " auto change element reference " << ret->getFullName() << " "
<< oldName << " -> " << newName);
}
}
}
}
if (notify) {
aboutToSetValue();
}
auto updateSub = [&](const std::string& newSub) {
if (sub != newSub) {
// signalUpdateElementReference(sub, newSub);
sub = newSub;
}
};
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::hasMappedElementName(sub.c_str())) {
updateSub(shadow.first);
}
}
if (reverse) {
if (shadow.first.size() && Data::hasMappedElementName(sub.c_str())) {
updateSub(shadow.first);
}
else {
updateSub(shadow.second);
}
return true;
}
if (missing) {
if (sub != shadow.first) {
updateSub(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);
std::string newSub(sub);
newSub.replace(pos, sub.size() - pos, &shadow.first[pos2]);
updateSub(newSub);
}
}
else if (sub != shadow.second) {
FC_LOG("element reference update " << sub << " -> " << shadow.second);
updateSub(shadow.second);
}
return true;
#else
(void)feature;
(void)obj;
(void)reverse;
(void)notify;
shadow.second = sub;
return false;
#endif
}
std::pair<DocumentObject*, std::string>
@@ -290,41 +508,62 @@ PropertyLinkBase::tryReplaceLink(const PropertyContainer *owner, DocumentObject
std::pair<DocumentObject*, std::string> res;
res.first = 0;
if(oldObj == obj) {
if(owner == parent) {
if (oldObj == obj) {
if (owner == parent) {
res.first = newObj;
if(subname) res.second = subname;
if (subname) {
res.second = subname;
}
return res;
}
return res;
#ifdef FC_USE_TNP_FIX
}
if(!subname || !subname[0])
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);
#endif
}
if (!subname || !subname[0]) {
return res;
}
App::DocumentObject *prev = obj;
App::DocumentObject* prev = obj;
std::size_t prevPos = 0;
std::string sub = subname;
for(auto pos=sub.find('.');pos!=std::string::npos;pos=sub.find('.',pos)) {
for (auto pos = sub.find('.'); pos != std::string::npos; pos = sub.find('.', pos)) {
++pos;
char c = sub[pos];
sub[pos] = 0;
auto sobj = obj->getSubObject(sub.c_str());
sub[pos] = c;
if(!sobj)
if (!sobj) {
break;
if(sobj == oldObj) {
if(prev == parent) {
if(sub[prevPos] == '$')
sub.replace(prevPos+1,pos-1-prevPos,newObj->Label.getValue());
else
sub.replace(prevPos,pos-1-prevPos,newObj->getNameInDocument());
}
if (sobj == oldObj) {
if (prev == parent) {
if (sub[prevPos] == '$') {
sub.replace(prevPos + 1, pos - 1 - prevPos, newObj->Label.getValue());
}
else {
sub.replace(prevPos, pos - 1 - prevPos, newObj->getNameInDocument());
}
res.first = obj;
res.second = std::move(sub);
return res;
}
break;
}else if(prev == parent)
#ifdef FC_USE_TNP_FIX
}
else if (sobj == newObj) {
return tryReplaceLink(owner, obj, parent, newObj, oldObj, subname);
#endif
}
else if (prev == parent) {
break;
}
prev = sobj;
prevPos = pos;
}
@@ -381,7 +620,6 @@ TYPESYSTEM_SOURCE(App::PropertyLinkHidden , App::PropertyLink)
PropertyLink::PropertyLink() = default;
PropertyLink::~PropertyLink()
{
resetLink();
@@ -910,18 +1148,19 @@ void PropertyLinkSub::setValue(App::DocumentObject * lValue,
std::vector<std::string> &&subs, std::vector<ShadowSub> &&shadows)
{
auto parent = Base::freecad_dynamic_cast<App::DocumentObject>(getContainer());
if(lValue) {
if(!lValue->isAttachedToDocument())
if (lValue) {
if (!lValue->isAttachedToDocument())
throw Base::ValueError("PropertyLinkSub: invalid document object");
if(!testFlag(LinkAllowExternal) && parent && parent->getDocument()!=lValue->getDocument())
if (!testFlag(LinkAllowExternal) && parent
&& parent->getDocument() != lValue->getDocument())
throw Base::ValueError("PropertyLinkSub does not support external object");
}
aboutToSetValue();
#ifndef USE_OLD_DAG
if(parent) {
if (parent) {
// before accessing internals make sure the object is not about to be destroyed
// otherwise the backlink contains dangling pointers
if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden) {
if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope != LinkScope::Hidden) {
if (_pcLinkSub)
_pcLinkSub->_removeBackLink(parent);
if (lValue)
@@ -929,11 +1168,12 @@ void PropertyLinkSub::setValue(App::DocumentObject * lValue,
}
}
#endif
_pcLinkSub=lValue;
_cSubList=std::move(subs);
if(shadows.size()==_cSubList.size())
_pcLinkSub = lValue;
_cSubList = std::move(subs);
if (shadows.size() == _cSubList.size()) {
_ShadowSubList = std::move(shadows);
else
onContainerRestored(); // re-register element references
} else
updateElementReference(nullptr);
checkLabelReferences(_cSubList);
hasSetValue();
@@ -950,13 +1190,26 @@ const std::vector<std::string>& PropertyLinkSub::getSubValues() 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.empty())
return shadow.second;
}else if(!shadow.first.empty())
}else if(!shadow.first.empty()) {
#ifdef FC_USE_TNP_FIX
if (Data::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;
}
}
#else
(void) tmp;
#endif
return shadow.first;
}
return subName;
}
@@ -964,13 +1217,27 @@ 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
{
#ifdef FC_USE_TNP_FIX
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::findElementName(sub.c_str());
if(element && boost::starts_with(element,starter))
ret.emplace_back(element);
}
return ret;
#else
(void)newStyle;
std::vector<std::string> temp;
@@ -980,6 +1247,7 @@ std::vector<std::string> PropertyLinkSub::getSubValuesStartsWith(const char* sta
}
}
return temp;
#endif
}
App::DocumentObject * PropertyLinkSub::getValue(Base::Type t) const
@@ -993,8 +1261,14 @@ PyObject *PropertyLinkSub::getPyObject()
Py::List list(static_cast<int>(_cSubList.size()));
if (_pcLinkSub) {
tup[0] = Py::asObject(_pcLinkSub->getPyObject());
#ifdef FC_USE_TNP_FIX
int i = 0;
for (auto &sub : getSubValues(true))
list[i++] = Py::String(sub);
#else
for(unsigned int i = 0;i<_cSubList.size(); i++)
list[i] = Py::String(_cSubList[i]);
#endif
tup[1] = list;
return Py::new_reference_to(tup);
}
@@ -1460,6 +1734,9 @@ Property *PropertyLinkSub::Copy() const
PropertyLinkSub *p= new PropertyLinkSub();
p->_pcLinkSub = _pcLinkSub;
p->_cSubList = _cSubList;
#ifdef FC_USE_TNP_FIX
p->_ShadowSubList = _ShadowSubList;
#endif
return p;
}
@@ -1468,7 +1745,12 @@ void PropertyLinkSub::Paste(const Property &from)
if(!from.isDerivedFrom(PropertyLinkSub::getClassTypeId()))
throw Base::TypeError("Incompatible property to paste to");
auto &link = static_cast<const PropertyLinkSub&>(from);
#ifdef FC_USE_TNP_FIX
setValue(link._pcLinkSub, link._cSubList,
std::vector<ShadowSub>(link._ShadowSubList));
#else
setValue(link._pcLinkSub, link._cSubList);
#endif
}
void PropertyLinkSub::getLinks(std::vector<App::DocumentObject *> &objs,
@@ -1727,9 +2009,10 @@ void PropertyLinkSubList::setValues(std::vector<DocumentObject*>&& lValue,
aboutToSetValue();
_lValueList = std::move(lValue);
_lSubList = std::move(lSubNames);
if(ShadowSubList.size()==_lSubList.size())
if(ShadowSubList.size()==_lSubList.size()) {
_ShadowSubList = std::move(ShadowSubList);
else
onContainerRestored(); // re-register element references
} else
updateElementReference(nullptr);
checkLabelReferences(_lSubList);
hasSetValue();
@@ -1914,14 +2197,26 @@ void PropertyLinkSubList::setSubListValues(const std::vector<PropertyLinkSubList
{
std::vector<DocumentObject*> links;
std::vector<std::string> subs;
#ifdef FC_USE_TNP_FIX
for (std::vector<PropertyLinkSubList::SubSet>::const_iterator it = values.begin(); it != values.end(); ++it) {
if (it->second.empty()) {
links.push_back(it->first);
subs.emplace_back();
continue;
}
for (std::vector<std::string>::const_iterator jt = it->second.begin(); jt != it->second.end(); ++jt) {
links.push_back(it->first);
subs.push_back(*jt);
}
}
#else
for (const auto & value : values) {
for (const auto& jt : value.second) {
links.push_back(value.first);
subs.push_back(jt);
}
}
#endif
setValues(links, subs);
}
@@ -2386,6 +2681,9 @@ Property *PropertyLinkSubList::Copy() const
PropertyLinkSubList *p = new PropertyLinkSubList();
p->_lValueList = _lValueList;
p->_lSubList = _lSubList;
#ifdef FC_USE_TNP_FIX
p->_ShadowSubList = _ShadowSubList;
#endif
return p;
}
@@ -2394,7 +2692,12 @@ void PropertyLinkSubList::Paste(const Property &from)
if(!from.isDerivedFrom(PropertyLinkSubList::getClassTypeId()))
throw Base::TypeError("Incompatible property to paste to");
auto &link = static_cast<const PropertyLinkSubList&>(from);
#ifdef FC_USE_TNP_FIX
setValues(link._lValueList, link._lSubList,
std::vector<ShadowSub>(link._ShadowSubList));
#else
setValues(link._lValueList, link._lSubList);
#endif
}
unsigned int PropertyLinkSubList::getMemSize () const
@@ -2409,8 +2712,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;
}
@@ -2965,9 +3269,12 @@ void PropertyXLink::setSubValues(std::vector<std::string> &&subs,
{
_SubList = std::move(subs);
_ShadowSubList.clear();
if(shadows.size() == _SubList.size())
if(shadows.size() == _SubList.size()) {
_ShadowSubList = std::move(shadows);
else
#ifdef FC_USE_TNP_FIX
onContainerRestored(); // re-register element references
#endif
} else
updateElementReference(nullptr);
checkLabelReferences(_SubList);
}
@@ -3468,8 +3775,12 @@ void PropertyXLink::copyTo(PropertyXLink &other,
}
if(subs)
other._SubList = std::move(*subs);
else
else {
other._SubList = _SubList;
#ifdef FC_USE_TNP_FIX
other._ShadowSubList = _ShadowSubList;
#endif
}
other._Flags = _Flags;
}
@@ -3497,10 +3808,19 @@ void PropertyXLink::Paste(const Property &from)
FC_WARN("Object '" << other.docName << '#' << other.objectName << "' not found");
return;
}
#ifdef FC_USE_TNP_FIX
setValue(obj,std::vector<std::string>(other._SubList),
std::vector<ShadowSub>(other._ShadowSubList));
} else
setValue(std::string(other.filePath),std::string(other.objectName),
std::vector<std::string>(other._SubList),
std::vector<ShadowSub>(other._ShadowSubList));
#else
setValue(obj,std::vector<std::string>(other._SubList));
} else
setValue(std::string(other.filePath),std::string(other.objectName),
std::vector<std::string>(other._SubList));
#endif
setFlag(LinkAllowPartial,other.testFlag(LinkAllowPartial));
}
@@ -3652,7 +3972,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,
@@ -3683,8 +4003,9 @@ 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;
}
+1
View File
@@ -1169,6 +1169,7 @@ protected:
std::vector<ShadowSub> _ShadowSubList;
std::vector<int> _mapped;
PropertyLinkBase *parentProp;
mutable std::string tmpShadow;
};
+1
View File
@@ -76,6 +76,7 @@
#include <memory>
#include <mutex>
#include <bitset>
#include <algorithm>
// streams
#include <iostream>
+1
View File
@@ -26,6 +26,7 @@
#ifndef _PreComp_
#include <mutex>
#include <vector>
#include <algorithm>
#endif
#include "Sequencer.h"
+1
View File
@@ -194,6 +194,7 @@ Tomas Pavlicek (pavltom)
totyg
triplus
trzyha
Turan Furkan Topak
ulrich1a
UR_
Uwe Stöhr (donovaly)
+2 -1
View File
@@ -23,6 +23,7 @@
#include "PreCompiled.h"
#include "ArcEngine.h"
#include <limits>
#include <vector>
# include <Inventor/engines/SoCalculator.h>
@@ -132,4 +133,4 @@ void ArcEngine::defaultValues()
SO_ENGINE_OUTPUT(pointCount, SoSFInt32, setValue(2));
SbVec3f point3(7.07f, 7.07f, 0.0);
SO_ENGINE_OUTPUT(midpoint, SoSFVec3f, setValue(point3));
}
}
+4
View File
@@ -173,6 +173,10 @@ MDIViewPyWrap::MDIViewPyWrap(const Py::Object& py, Gui::Document* pcDocument,QWi
QWidget* widget = ptr->widget();
if (widget) {
setCentralWidget(widget);
QString title = widget->windowTitle();
if (!title.isEmpty()) {
setWindowTitle(title);
}
}
}
catch (Py::Exception&) {
-3
View File
@@ -60,14 +60,11 @@ public:
void setMeasureObject(Measure::MeasureBase* obj);
private:
QColumnView* dialog{nullptr};
void onSelectionChanged(const Gui::SelectionChanges& msg) override;
Measure::MeasureBase *_mMeasureObject = nullptr;
QLineEdit* valueResult{nullptr};
QLabel* labelResult{nullptr};
QComboBox* modeSwitch{nullptr};
void removeObject();
@@ -78,7 +78,7 @@ class WidgetProgressBar(QtWidgets.QWidget):
self.progress_bar.setMaximum(_TOTAL_INCREMENTS)
self.stop_button.clicked.connect(self.stop_clicked)
self.stop_button.setIcon(
QtGui.QIcon.fromTheme("stop", QtGui.QIcon(":/icons/media-playback-stop.svg"))
QtGui.QIcon.fromTheme("stop", QtGui.QIcon(":/icons/debug-stop.svg"))
)
self.vertical_layout.addLayout(self.horizontal_layout)
self.vertical_layout.addWidget(self.status_label)
+1
View File
@@ -259,6 +259,7 @@ class CurtainWall(ArchComponent.Component):
obj.addProperty("App::PropertyVector","VerticalDirection","CurtainWall",
QT_TRANSLATE_NOOP("App::Property","The vertical direction reference to be used by this object to deduce vertical/horizontal directions. Keep it close to the actual vertical direction of your curtain wall"))
obj.VerticalDirection = FreeCAD.Vector(0,0,1)
self.Type = "CurtainWall"
def onDocumentRestored(self,obj):
+1
View File
@@ -332,6 +332,7 @@ class _ArchSchedule:
q = None
if obj.Unit[i]:
unit = obj.Unit[i]
unit = unit.replace("^","") # get rid of existing power symbol
unit = unit.replace("2","^2")
unit = unit.replace("3","^3")
unit = unit.replace("²","^2")
+5 -1
View File
@@ -163,7 +163,11 @@ target_link_libraries(area area-native ${area_LIBS} ${area_native_LIBS})
# TODO why CMAKE_SHARED_LINKER_FLAGS is not used here?
# This is a dirty workaround!
if(NOT BUILD_DYNAMIC_LINK_PYTHON AND CMAKE_COMPILER_IS_CLANGXX)
target_link_libraries(area "-Wl,--undefined,dynamic_lookup")
if(APPLE)
target_link_libraries(area "-Wl,-undefined,dynamic_lookup")
else(UNIX)
target_link_libraries(area "-Wl,--undefined,dynamic_lookup")
endif()
endif()
SET_BIN_DIR(area area /Mod/CAM)
+1
View File
@@ -77,6 +77,7 @@ class Line(gui_base_original.Creator):
self.obj = self.doc.addObject("Part::Feature", self.featureName)
gui_utils.format_object(self.obj)
self.obj.ViewObject.ShowInTree = False
self.call = self.view.addEventCallback("SoEvent", self.action)
_toolmsg(translate("draft", "Pick first point"))
@@ -45,8 +45,7 @@ class ViewProviderPoint(ViewProviderDraft):
vobj.setEditorMode('DisplayMode', mode)
vobj.setEditorMode('Lighting', mode)
vobj.setEditorMode('LineMaterial', mode)
vobj.setEditorMode('ShapeColor', mode)
vobj.setEditorMode('ShapeMaterial', mode)
vobj.setEditorMode('ShapeAppearance', mode)
vobj.setEditorMode('Transparency', mode)
def getIcon(self):
+1 -1
View File
@@ -67,7 +67,7 @@ public:
virtual QString getResultString();
virtual std::vector<std::string> getInputProps();
virtual App::Property* getResultProp() {return {};};
virtual App::Property* getResultProp() {return {};}
virtual Base::Placement getPlacement();
// Return the objects that are measured
+1 -1
View File
@@ -63,7 +63,7 @@ public:
App::Property* getResultProp() override {return &this->Length;}
// Return a placement for the viewprovider, just use the first element for now
Base::Placement getPlacement();
Base::Placement getPlacement() override;
// Return the object we are measuring
std::vector<App::DocumentObject*> getSubject() const override;
+2 -3
View File
@@ -28,6 +28,5 @@
using namespace std;
void CreateMeasureCommands() {
Base::Console().Message("Init MeasureGui\n");
Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager();
}
Base::Console().Log("Init MeasureGui\n");
}
-7
View File
@@ -53,13 +53,6 @@
#include <Base/Rotation.h>
#include <Base/Vector3D.h>
#include <Mod/Measure/App/MeasureAngle.h>
#include <Mod/Measure/App/MeasureDistance.h>
#include <Mod/Measure/App/MeasureLength.h>
#include <Mod/Measure/App/MeasurePosition.h>
#include <Mod/Measure/App/MeasureArea.h>
#include <Mod/Measure/App/MeasureRadius.h>
#include "VectorAdapter.h"
#include "PartFeature.h"
+1
View File
@@ -4081,6 +4081,7 @@ bool TopoShape::findPlane(gp_Pln& pln, double tol, double atol) const
}
#else
bool TopoShape::findPlane(gp_Pln &pln, double tol, double atol) const {
(void)atol;
if(_Shape.IsNull())
return false;
TopoDS_Shape shape = _Shape;
+68 -1
View File
@@ -261,6 +261,13 @@ enum class OpenResult
allowOpenResult
};
// See BRepFeat_MakeRevol
enum class RevolMode {
CutFromBase = 0,
FuseWithBase = 1,
None = 2
};
/** The representation for a CAD Shape
*/
// NOLINTNEXTLINE cppcoreguidelines-special-member-functions
@@ -1081,7 +1088,6 @@ public:
/** Make revolved shell around a basis shape
*
* @param base: the basis shape
* @param axis: the revolving axis
* @param d: rotation angle in degree
* @param face_maker: optional type name of the the maker used to make a
@@ -1097,6 +1103,67 @@ public:
}
/** Make revolved shell around a basis shape
*
* @param base: the basis shape
* @param axis: the revolving axis
* @param d: rotation angle in degree
* @param face_maker: optional type name of the the maker used to make a
* face from basis shape
* @param supportface: the bottom face for the revolution, or null
* @param uptoface: the upper limit face for the revolution, or null
* @param Mode: the opencascade defined modes
* @param Modify: if opencascade should modify existing shapes
* @param op: optional string to be encoded into topo naming for indicating
* the operation
*
* @return Return the generated new shape. The TopoShape itself is not modified.
*/
TopoShape& makeElementRevolution(const TopoShape& _base,
const gp_Ax1& axis,
double d,
const TopoDS_Face& supportface,
const TopoDS_Face& uptoface,
const char* face_maker = nullptr,
RevolMode Mode = RevolMode::None,
Standard_Boolean Modify = Standard_True,
const char* op = nullptr);
/** Make revolved shell around a basis shape
*
* @param axis: the revolving axis
* @param d: rotation angle in degree
* @param face_maker: optional type name of the the maker used to make a
* face from basis shape
* @param supportface: the bottom face for the revolution, or null
* @param uptoface: the upper limit face for the revolution, or null
* @param Mode: the opencascade defined modes
* @param Modify: if opencascade should modify existing shapes
* @param op: optional string to be encoded into topo naming for indicating
* the operation
*
* @return Return the generated new shape. The TopoShape itself is not modified.
*/
TopoShape& makeElementRevolution(const gp_Ax1& axis,
double d,
const TopoDS_Face& supportface,
const TopoDS_Face& uptoface,
const char* face_maker = nullptr,
RevolMode Mode = RevolMode::None,
Standard_Boolean Modify = Standard_True,
const char* op = nullptr) const
{
return TopoShape(0, Hasher).makeElementRevolution(*this,
axis,
d,
supportface,
uptoface,
face_maker,
Mode,
Modify,
op);
}
/** Make a prism that is a linear sweep of a basis shape
*
* @param base: the basis shape
+3 -3
View File
@@ -358,11 +358,11 @@ Part.show(s)
split(paramval) -> Wire
--
Args:
paramval (float or int): The parameter value along the Edge at which to
paramval (float or list_of_floats): The parameter values along the Edge at which to
split it e.g:
x = Part.makeCircle(1, FreeCAD.Vector(0,0,0), FreeCAD.Vector(0,0,1), 0, 90)
y = x.derivative3At(x.FirstParameter + 0.5 * (x.LastParameter - x.FirstParameter))
edge = Part.makeCircle(1, FreeCAD.Vector(0,0,0), FreeCAD.Vector(0,0,1), 0, 90)
wire = edge.split([0.5, 1.0])
Returns:
Wire: wire made up of two Edges
+8 -2
View File
@@ -711,10 +711,16 @@ PyObject* TopoShapeEdgePy::split(PyObject *args)
BRepBuilderAPI_MakeWire mkWire;
Handle(Geom_Curve) c = adapt.Curve().Curve();
const TopoDS_Edge& edge = TopoDS::Edge(this->getTopoShapePtr()->getShape());
BRep_Builder builder;
TopoDS_Edge e;
std::vector<Standard_Real>::iterator end = par.end() - 1;
for (std::vector<Standard_Real>::iterator it = par.begin(); it != end; ++it) {
BRepBuilderAPI_MakeEdge mkBuilder(c, it[0], it[1]);
mkWire.Add(mkBuilder.Edge());
BRepBuilderAPI_MakeEdge mke(c, it[0], it[1]);
e = mke.Edge();
builder.Transfert(edge, e);
builder.Range(e, it[0], it[1], false);
mkWire.Add(e);
}
return new TopoShapeWirePy(new TopoShape(mkWire.Shape()));
+46
View File
@@ -106,6 +106,7 @@
#include <App/ElementNamingUtils.h>
#include <ShapeAnalysis_FreeBoundsProperties.hxx>
#include <BRepBuilderAPI_MakeSolid.hxx>
#include <BRepFeat_MakeRevol.hxx>
FC_LOG_LEVEL_INIT("TopoShape", true, true) // NOLINT
@@ -4439,6 +4440,51 @@ TopoShape& TopoShape::makeElementRevolve(const TopoShape& _base,
return makeElementShape(mkRevol, base, op);
}
TopoShape& TopoShape::makeElementRevolution(const TopoShape& _base,
const gp_Ax1& axis,
double d,
const TopoDS_Face& supportface,
const TopoDS_Face& uptoface,
const char* face_maker,
RevolMode Mode,
Standard_Boolean Modify,
const char* op)
{
if (!op) {
op = Part::OpCodes::Revolve;
}
TopoShape base(_base);
if (base.isNull()) {
FC_THROWM(NullShapeException, "Null shape");
}
if (face_maker && !base.hasSubShape(TopAbs_FACE)) {
if (!base.hasSubShape(TopAbs_WIRE)) {
base = base.makeElementWires();
}
base = base.makeElementFace(nullptr, face_maker, nullptr);
}
BRepFeat_MakeRevol mkRevol;
for (TopExp_Explorer xp(base.getShape(), TopAbs_FACE); xp.More(); xp.Next()) {
mkRevol.Init(_base.getShape(),
xp.Current(),
supportface,
axis,
static_cast<int>(Mode),
Modify);
mkRevol.Perform(uptoface);
if (!mkRevol.IsDone()) {
throw Base::RuntimeError("Revolution: Up to face: Could not revolve the sketch!");
}
base = mkRevol.Shape();
if (Mode == RevolMode::None) {
Mode = RevolMode::FuseWithBase;
}
}
return makeElementShape(mkRevol, base, op);
}
TopoShape& TopoShape::makeElementDraft(const TopoShape& shape,
const std::vector<TopoShape>& _faces,
const gp_Dir& pullDirection,
+24 -1
View File
@@ -10,7 +10,7 @@ class BRepTests(unittest.TestCase):
This is a unit test for PR #13507
"""
num = 18
alt = [0,1] * num
alt = [0, 1] * num
pts = [App.Vector(i, alt[i], 0) for i in range(num)]
bsc = Part.BSplineCurve()
@@ -24,3 +24,26 @@ class BRepTests(unittest.TestCase):
self.assertFalse(proj.isNull())
self.assertEqual(len(proj.Edges), 1)
def testEdgeSplitFace(self):
coords2d = [(0.5, -0.5), (1.0, -0.5), (1.0, 0.5), (0.5, 0.5)]
pts2d = [App.Base.Vector2d(u, v) for u, v in coords2d]
pts2d.append(pts2d[0])
sphere = Part.Sphere()
edges = []
for i in range(1, len(pts2d)):
ls = Part.Geom2d.Line2dSegment(pts2d[i - 1], pts2d[i])
edges.append(ls.toShape(sphere))
split = edges[0].split(0.25)
new_edges = split.Edges + edges[1:]
wire = Part.Wire(new_edges)
face = Part.Face(wire, "Part::FaceMakerSimple")
self.assertTrue(face.isValid())
def testEdgeSplitReplace(self):
cyl = Part.makeCylinder(2, 5)
e1 = cyl.Edge3
split = e1.split([1.0, 2.0])
newcyl = cyl.replaceShape([(e1, split), (cyl.Vertex2, split.Vertex1)])
self.assertTrue(newcyl.isValid())
@@ -431,7 +431,6 @@ App::DocumentObjectExecReturn* FeatureExtrude::buildExtrusion(ExtrudeOptions opt
bool makeface = options.testFlag(ExtrudeOption::MakeFace);
bool fuse = options.testFlag(ExtrudeOption::MakeFuse);
bool legacyPocket = options.testFlag(ExtrudeOption::LegacyPocket);
bool inverseDirection = options.testFlag(ExtrudeOption::InverseDirection);
std::string method(Type.getValueAsString());
+154 -74
View File
@@ -38,6 +38,7 @@
#include <Base/Tools.h>
#include "FeatureRevolution.h"
#include "Mod/Part/App/TopoShapeOpCode.h"
using namespace PartDesign;
@@ -78,24 +79,29 @@ short Revolution::mustExecute() const
return ProfileBased::mustExecute();
}
App::DocumentObjectExecReturn *Revolution::execute()
App::DocumentObjectExecReturn* Revolution::execute()
{
// Validate parameters
// All angles are in radians unless explicitly stated
double angleDeg = Angle.getValue();
if (angleDeg > 360.0)
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Angle of revolution too large"));
if (angleDeg > 360.0) {
return new App::DocumentObjectExecReturn(
QT_TRANSLATE_NOOP("Exception", "Angle of revolution too large"));
}
double angle = Base::toRadians<double>(angleDeg);
if (angle < Precision::Angular())
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Angle of revolution too small"));
if (angle < Precision::Angular()) {
return new App::DocumentObjectExecReturn(
QT_TRANSLATE_NOOP("Exception", "Angle of revolution too small"));
}
double angle2 = Base::toRadians(Angle2.getValue());
TopoDS_Shape sketchshape;
TopoShape sketchshape;
try {
sketchshape = getVerifiedFace();
} catch (const Base::Exception& e) {
}
catch (const Base::Exception& e) {
return new App::DocumentObjectExecReturn(e.what());
}
@@ -103,27 +109,36 @@ App::DocumentObjectExecReturn *Revolution::execute()
TopoShape base;
try {
base = getBaseTopoShape();
} catch (const Base::Exception&) {
}
catch (const Base::Exception&) {
// fall back to support (for legacy features)
base = TopoShape();
}
// update Axis from ReferenceAxis
try {
updateAxis();
} catch (const Base::Exception& e) {
}
catch (const Base::Exception& e) {
return new App::DocumentObjectExecReturn(e.what());
}
// get revolve axis
Base::Vector3d b = Base.getValue();
gp_Pnt pnt(b.x,b.y,b.z);
Base::Vector3d v = Axis.getValue();
gp_Dir dir(v.x,v.y,v.z);
try {
if (sketchshape.IsNull())
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Creating a face from sketch failed"));
// get revolve axis
Base::Vector3d b = Base.getValue();
gp_Pnt pnt(b.x, b.y, b.z);
Base::Vector3d v = Axis.getValue();
if (v.IsNull()) {
return new App::DocumentObjectExecReturn(
QT_TRANSLATE_NOOP("Exception", "Reference axis is invalid"));
}
gp_Dir dir(v.x, v.y, v.z);
if (sketchshape.isNull()) {
return new App::DocumentObjectExecReturn(
QT_TRANSLATE_NOOP("Exception", "Creating a face from sketch failed"));
}
RevolMethod method = methodFromString(Type.getValueAsString());
@@ -132,51 +147,99 @@ App::DocumentObjectExecReturn *Revolution::execute()
pnt.Transform(invObjLoc.Transformation());
dir.Transform(invObjLoc.Transformation());
base.move(invObjLoc);
sketchshape.Move(invObjLoc);
sketchshape.move(invObjLoc);
// Check distance between sketchshape and axis - to avoid failures and crashes
TopExp_Explorer xp;
xp.Init(sketchshape, TopAbs_FACE);
for (;xp.More(); xp.Next()) {
if (checkLineCrossesFace(gp_Lin(pnt, dir), TopoDS::Face(xp.Current())))
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Revolve axis intersects the sketch"));
xp.Init(sketchshape.getShape(), TopAbs_FACE);
for (; xp.More(); xp.Next()) {
if (checkLineCrossesFace(gp_Lin(pnt, dir), TopoDS::Face(xp.Current()))) {
return new App::DocumentObjectExecReturn(
QT_TRANSLATE_NOOP("Exception", "Revolve axis intersects the sketch"));
}
}
// Create a fresh support even when base exists so that it can be used for patterns
#ifdef FC_USE_TNP_FIX
TopoShape result;
#else
TopoDS_Shape result;
#endif
TopoDS_Face supportface = getSupportFace();
supportface.Move(invObjLoc);
if (method == RevolMethod::ToFace || method == RevolMethod::ToFirst || method == RevolMethod::ToLast) {
if (method == RevolMethod::ToFace || method == RevolMethod::ToFirst
|| method == RevolMethod::ToLast) {
TopoDS_Face upToFace;
if (method == RevolMethod::ToFace) {
getFaceFromLinkSub(upToFace, UpToFace);
upToFace.Move(invObjLoc);
}
else
throw Base::RuntimeError("ProfileBased: Revolution up to first/last is not yet supported");
else {
throw Base::RuntimeError(
"ProfileBased: Revolution up to first/last is not yet supported");
}
// TODO: This method is designed for extrusions. needs to be adapted for revolutions.
// getUpToFace(upToFace, base, supportface, sketchshape, method, dir);
TopoDS_Face supportface = getSupportFace();
// TopoDS_Face supportface = getSupportFace();
supportface.Move(invObjLoc);
if (Reversed.getValue())
if (Reversed.getValue()) {
dir.Reverse();
}
TopExp_Explorer Ex(supportface,TopAbs_WIRE);
if (!Ex.More())
TopExp_Explorer Ex(supportface, TopAbs_WIRE);
if (!Ex.More()) {
supportface = TopoDS_Face();
}
RevolMode mode = RevolMode::None;
generateRevolution(result, base.getShape(), sketchshape, supportface, upToFace, gp_Ax1(pnt, dir), method, mode, Standard_True);
#ifdef FC_USE_TNP_FIX
// revolve the face to a solid
// TopoShape result(0);
try {
result = base.makeElementRevolution(gp_Ax1(pnt, dir), angle, supportface, upToFace);
}
catch (Standard_Failure&) {
return new App::DocumentObjectExecReturn("Could not revolve the sketch!");
}
#else
generateRevolution(result,
base.getShape(),
sketchshape.getShape(),
supportface,
upToFace,
gp_Ax1(pnt, dir),
method,
mode,
Standard_True);
#endif
}
else {
bool midplane = Midplane.getValue();
bool reversed = Reversed.getValue();
generateRevolution(result, sketchshape, gp_Ax1(pnt, dir), angle, angle2, midplane, reversed, method);
generateRevolution(result,
sketchshape.getShape(),
gp_Ax1(pnt, dir),
angle,
angle2,
midplane,
reversed,
method);
}
#ifdef FC_USE_TNP_FIX
if (!result.isNull()) {
result = refineShapeIfActive(result);
// set the additive shape property for later usage in e.g. pattern
this->AddSubShape.setValue(result);
if (!base.isNull()) {
result = result.makeElementFuse(base);
result = refineShapeIfActive(result);
}
#else
if (!result.IsNull()) {
result = refineShapeIfActive(result);
// set the additive shape property for later usage in e.g. pattern
@@ -186,16 +249,21 @@ App::DocumentObjectExecReturn *Revolution::execute()
// Let's call algorithm computing a fuse operation:
BRepAlgoAPI_Fuse mkFuse(base.getShape(), result);
// Let's check if the fusion has been successful
if (!mkFuse.IsDone())
throw Part::BooleanException(QT_TRANSLATE_NOOP("Exception", "Fusion with base feature failed"));
if (!mkFuse.IsDone()) {
throw Part::BooleanException(
QT_TRANSLATE_NOOP("Exception", "Fusion with base feature failed"));
}
result = mkFuse.Shape();
result = refineShapeIfActive(result);
}
#endif
this->Shape.setValue(getSolid(result));
}
else
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Could not revolve the sketch!"));
else {
return new App::DocumentObjectExecReturn(
QT_TRANSLATE_NOOP("Exception", "Could not revolve the sketch!"));
}
// eventually disable some settings that are not valid for the current method
updateProperties(method);
@@ -204,11 +272,15 @@ App::DocumentObjectExecReturn *Revolution::execute()
}
catch (Standard_Failure& e) {
if (std::string(e.GetMessageString()) == "TopoDS::Face")
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Could not create face from sketch.\n"
"Intersecting sketch entities in a sketch are not allowed."));
else
if (std::string(e.GetMessageString()) == "TopoDS::Face") {
return new App::DocumentObjectExecReturn(
QT_TRANSLATE_NOOP("Exception",
"Could not create face from sketch.\n"
"Intersecting sketch entities in a sketch are not allowed."));
}
else {
return new App::DocumentObjectExecReturn(e.GetMessageString());
}
}
catch (Base::Exception& e) {
return new App::DocumentObjectExecReturn(e.what());
@@ -257,7 +329,12 @@ Revolution::RevolMethod Revolution::methodFromString(const std::string& methodSt
return RevolMethod::Dimension;
}
#ifdef FC_USE_TNP_FIX
void Revolution::generateRevolution(TopoShape& revol,
#else
void Revolution::generateRevolution(TopoDS_Shape& revol,
#endif
const TopoDS_Shape& sketchshape,
const gp_Ax1& axis,
const double angle,
@@ -267,46 +344,49 @@ void Revolution::generateRevolution(TopoDS_Shape& revol,
RevolMethod method)
{
if (method == RevolMethod::Dimension || method == RevolMethod::TwoDimensions || method == RevolMethod::ThroughAll) {
double angleTotal = angle;
double angleOffset = 0.;
double angleTotal = angle;
double angleOffset = 0.;
if (method == RevolMethod::TwoDimensions) {
// Rotate the face by `angle2`/`angle` to get "second" angle
angleTotal += angle2;
angleOffset = angle2 * -1.0;
}
else if (midplane) {
// Rotate the face by half the angle to get Revolution symmetric to sketch plane
angleOffset = -angle / 2;
}
if (method == RevolMethod::TwoDimensions) {
// Rotate the face by `angle2`/`angle` to get "second" angle
angleTotal += angle2;
angleOffset = angle2 * -1.0;
}
else if (midplane) {
// Rotate the face by half the angle to get Revolution symmetric to sketch plane
angleOffset = -angle / 2;
}
if (fabs(angleTotal) < Precision::Angular())
throw Base::ValueError("Cannot create a revolution with zero angle.");
if (fabs(angleTotal) < Precision::Angular())
throw Base::ValueError("Cannot create a revolution with zero angle.");
gp_Ax1 revolAx(axis);
if (reversed) {
revolAx.Reverse();
}
gp_Ax1 revolAx(axis);
if (reversed) {
revolAx.Reverse();
}
TopoDS_Shape from = sketchshape;
if (method == RevolMethod::TwoDimensions || midplane) {
gp_Trsf mov;
mov.SetRotation(revolAx, angleOffset);
TopLoc_Location loc(mov);
from.Move(loc);
}
TopoDS_Shape from = sketchshape;
if (method == RevolMethod::TwoDimensions || midplane) {
gp_Trsf mov;
mov.SetRotation(revolAx, angleOffset);
TopLoc_Location loc(mov);
from.Move(loc);
}
// revolve the face to a solid
// BRepPrimAPI is the only option that allows use of this shape for patterns.
// See https://forum.freecadweb.org/viewtopic.php?f=8&t=70185&p=611673#p611673.
BRepPrimAPI_MakeRevol RevolMaker(from, revolAx, angleTotal);
#ifdef FC_USE_TNP_FIX
revol = TopoShape(from).makeElementRevolve(revolAx,angleTotal);
#else
// revolve the face to a solid
// BRepPrimAPI is the only option that allows use of this shape for patterns.
// See https://forum.freecadweb.org/viewtopic.php?f=8&t=70185&p=611673#p611673.
BRepPrimAPI_MakeRevol RevolMaker(from, revolAx, angleTotal);
if (!RevolMaker.IsDone())
throw Base::RuntimeError("ProfileBased: RevolMaker failed! Could not revolve the sketch!");
else
revol = RevolMaker.Shape();
}
else {
if (!RevolMaker.IsDone())
throw Base::RuntimeError("ProfileBased: RevolMaker failed! Could not revolve the sketch!");
else
revol = RevolMaker.Shape();
#endif
} else {
std::stringstream str;
str << "ProfileBased: Internal error: Unknown method for generateRevolution()";
throw Base::RuntimeError(str.str());
@@ -95,7 +95,11 @@ protected:
/**
* Generates a revolution of the input sketchshape and stores it in the given \a revol.
*/
#ifdef FC_USE_TNP_FIX
void generateRevolution(TopoShape& revol,
#else
void generateRevolution(TopoDS_Shape& revol,
#endif
const TopoDS_Shape& sketchshape,
const gp_Ax1& ax1,
const double angle,
@@ -280,7 +280,7 @@ TopoDS_Shape ProfileBased::getVerifiedFace(bool silent) const {
}
TopoShape ProfileBased::getTopoShapeVerifiedFace(bool silent,
bool doFit,
[[maybe_unused]]bool doFit, // TODO: Remove parameter
bool allowOpen,
const App::DocumentObject* profile,
const std::vector<std::string>& _subs) const
@@ -498,14 +498,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
self.Doc.recompute()
# Assert
# self.assertEqual(len(body.Shape.childShapes()), 1)
if App.GuiUp:
# Todo: This triggers a 'hasher mismatch' warning in TopoShape::mapSubElement as called by
# flushElementMap. This appears to be the case whenever you have a parent with a hasher
# that has children without hashmaps. The warning seems to be spurious in this case, but
# perhaps there is a solution involving setting hashmaps on all elements.
self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 30)
else:
self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 26)
self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 30)
self.assertEqual(body.Shape.ElementMapSize,30)
self.assertEqual(sketch.Shape.ElementMapSize,12)
self.assertEqual(pad.Shape.ElementMapSize,30)
@@ -520,7 +513,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
return
# Act
revolution = self.Doc.addObject('PartDesign::Revolution', 'Revolution')
revolution.ReferenceAxis = body.Origin.OriginFeatures[1]
revolution.ReferenceAxis = (self.Doc.getObject('Sketch'),['V_Axis'])
revolution.Profile = sketch # Causing segfault
body.addObject(sketch)
body.addObject(revolution)
@@ -582,7 +575,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
# Act
helix = self.Doc.addObject('PartDesign::AdditiveHelix', 'Helix')
helix.Profile = sketch
helix.ReferenceAxis = body.Origin.OriginFeatures[2]
helix.ReferenceAxis = (self.Doc.getObject('Sketch'),['V_Axis'])
body.addObject(sketch)
body.addObject(helix)
self.Doc.recompute()
@@ -789,6 +782,174 @@ class TestTopologicalNamingProblem(unittest.TestCase):
self.assertEqual(plane.Shape.ElementMapSize, 0)
self.assertEqual(pad.Shape.ElementMapSize, 26)
def testChangeSketch(self):
# Arrange
self.Body = self.Doc.addObject('PartDesign::Body', 'Body')
# Make first offset cube Pad
self.PadSketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch')
self.Body.addObject(self.PadSketch)
TestSketcherApp.CreateRectangleSketch(self.PadSketch, (0, 0), (31.37, 25.2))
self.Doc.recompute()
self.Pad = self.Doc.addObject("PartDesign::Pad", "Pad")
self.Body.addObject(self.Pad)
self.Pad.Profile = self.PadSketch
self.Pad.Length = 10
self.Doc.recompute()
self.Sketch001 = self.Body.newObject('Sketcher::SketchObject','Sketch001')
self.Sketch001.AttachmentSupport = (self.Doc.getObject('Pad'),['Face6',])
self.Sketch001.MapMode = 'FlatFace'
App.ActiveDocument.recompute()
self.Sketch001.addExternal("Pad","Edge10")
self.Sketch001.addExternal("Pad","Edge7")
geoList = []
geoList.append(Part.Circle(App.Vector(15.093666, 13.036922, 0.000000),
App.Vector(0.000000, 0.000000, 1.000000), 5.000000))
self.Sketch001.addGeometry(geoList,False)
del geoList
self.Sketch001.addConstraint(Sketcher.Constraint('Radius',0,5.000000))
self.Sketch001.addConstraint(Sketcher.Constraint('Symmetric',-3,2,-4,1,0,3))
App.ActiveDocument.recompute()
self.Doc.recompute()
self.Pad001 = self.Body.newObject('PartDesign::Pad','Pad001')
self.Pad001.Profile = self.Doc.getObject('Sketch001')
self.Pad001.Length = 10
App.ActiveDocument.recompute()
self.Pad001.ReferenceAxis = (self.Doc.getObject('Sketch001'),['N_Axis'])
self.Sketch001.Visibility = False
App.ActiveDocument.recompute()
self.Pad001.Length = 10.000000
self.Pad001.TaperAngle = 0.000000
self.Pad001.UseCustomVector = 0
self.Pad001.Direction = (0, 0, 1)
self.Pad001.ReferenceAxis = (self.Doc.getObject('Sketch001'), ['N_Axis'])
self.Pad001.AlongSketchNormal = 1
self.Pad001.Type = 0
self.Pad001.UpToFace = None
self.Pad001.Reversed = 0
self.Pad001.Midplane = 0
self.Pad001.Offset = 0
self.Doc.recompute()
self.Doc.getObject('Pad').Visibility = False
self.Doc.getObject('Sketch001').Visibility = False
# Modify the original sketch to generate TNP issue
geoList = []
geoList.append(Part.LineSegment(App.Vector(2.510468, 22.837425, 0.000000),
App.Vector(2.510468, 19.933617, 0.000000)))
geoList.append(Part.LineSegment(App.Vector(2.510468, 19.933617, 0.000000),
App.Vector(4.869811, 19.933617, 0.000000)))
geoList.append(Part.LineSegment(App.Vector(4.869811, 19.933617, 0.000000),
App.Vector(4.869811, 22.837425, 0.000000)))
geoList.append(Part.LineSegment(App.Vector(4.869811, 22.837425, 0.000000),
App.Vector(2.510468, 22.837425, 0.000000)))
self.PadSketch.addGeometry(geoList,False)
del geoList
constraintList = []
constraintList.append(Sketcher.Constraint('Coincident', 4, 2, 5, 1))
constraintList.append(Sketcher.Constraint('Coincident', 5, 2, 6, 1))
constraintList.append(Sketcher.Constraint('Coincident', 6, 2, 7, 1))
constraintList.append(Sketcher.Constraint('Coincident', 7, 2, 4, 1))
constraintList.append(Sketcher.Constraint('Vertical', 4))
constraintList.append(Sketcher.Constraint('Vertical', 6))
constraintList.append(Sketcher.Constraint('Horizontal', 5))
constraintList.append(Sketcher.Constraint('Horizontal', 7))
self.PadSketch.addConstraint(constraintList)
del constraintList
self.Doc.recompute()
# Assert
if self.Body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
return
self.assertEqual(self.Body.Shape.BoundBox.XMin,0)
self.assertEqual(self.Body.Shape.BoundBox.YMin,0)
self.assertEqual(self.Body.Shape.BoundBox.ZMin,0)
self.assertEqual(self.Body.Shape.BoundBox.XMax,31.37)
self.assertEqual(self.Body.Shape.BoundBox.YMax,25.2)
self.assertEqual(self.Body.Shape.BoundBox.ZMax,20)
def testApplyFillet(self):
# Arrange
self.Body = self.Doc.addObject('PartDesign::Body', 'Body')
# Make first offset cube Pad
self.PadSketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch')
self.Body.addObject(self.PadSketch)
TestSketcherApp.CreateRectangleSketch(self.PadSketch, (0, 0), (31.37, 25.2))
self.Doc.recompute()
self.Pad = self.Doc.addObject("PartDesign::Pad", "Pad")
self.Body.addObject(self.Pad)
self.Pad.Profile = self.PadSketch
self.Pad.Length = 10
self.Doc.recompute()
self.Sketch001 = self.Body.newObject('Sketcher::SketchObject','Sketch001')
self.Sketch001.AttachmentSupport = (self.Doc.getObject('Pad'),['Face6',])
self.Sketch001.MapMode = 'FlatFace'
App.ActiveDocument.recompute()
self.Sketch001.addExternal("Pad","Edge10")
self.Sketch001.addExternal("Pad","Edge7")
geoList = []
geoList.append(Part.Circle(App.Vector(15.093666, 13.036922, 0.000000),
App.Vector(0.000000, 0.000000, 1.000000), 5.000000))
self.Sketch001.addGeometry(geoList,False)
del geoList
self.Sketch001.addConstraint(Sketcher.Constraint('Radius',0,5.000000))
self.Sketch001.addConstraint(Sketcher.Constraint('Symmetric',-3,2,-4,1,0,3))
App.ActiveDocument.recompute()
self.Doc.recompute()
self.Pad001 = self.Body.newObject('PartDesign::Pad','Pad001')
self.Pad001.Profile = self.Doc.getObject('Sketch001')
self.Pad001.Length = 10
App.ActiveDocument.recompute()
self.Pad001.ReferenceAxis = (self.Doc.getObject('Sketch001'),['N_Axis'])
self.Sketch001.Visibility = False
App.ActiveDocument.recompute()
self.Pad001.Length = 10.000000
self.Pad001.TaperAngle = 0.000000
self.Pad001.UseCustomVector = 0
self.Pad001.Direction = (0, 0, 1)
self.Pad001.ReferenceAxis = (self.Doc.getObject('Sketch001'), ['N_Axis'])
self.Pad001.AlongSketchNormal = 1
self.Pad001.Type = 0
self.Pad001.UpToFace = None
self.Pad001.Reversed = 0
self.Pad001.Midplane = 0
self.Pad001.Offset = 0
self.Doc.recompute()
self.Doc.getObject('Pad').Visibility = False
self.Doc.getObject('Sketch001').Visibility = False
area1 = self.Pad.Shape.Area
# Act
self.Doc.getObject('Sketch').fillet(2,3,App.Vector(6.673934,25.000000,0),App.Vector(0.000000,21.980343,0),4.740471,True,True,False)
self.Doc.recompute()
# filleted = self.Pad001.Shape.makeFillet(1,self.Pad001.Shape.Edges[0:2])
# self.filleted = Part.show(filleted,"Filleted")
# self.Body.addObject(self.filleted)
area2 = self.Pad.Shape.Area
print(area1,area2)
# Assert
if self.Body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
return
self.assertEqual(self.Body.Shape.BoundBox.XMin,0)
self.assertEqual(self.Body.Shape.BoundBox.YMin,0)
self.assertEqual(self.Body.Shape.BoundBox.ZMin,0)
self.assertEqual(self.Body.Shape.BoundBox.XMax,31.37)
self.assertEqual(self.Body.Shape.BoundBox.YMax,25.2)
self.assertEqual(self.Body.Shape.BoundBox.ZMax,20)
def create_t_sketch(self):
self.Doc.getObject('Body').newObject('Sketcher::SketchObject', 'Sketch')
geo_list = [
@@ -996,7 +996,6 @@ void DrawSketchHandler::createAutoConstraints(const std::vector<AutoConstraint>&
geoId2);
} break;
case Sketcher::Symmetric: {
Sketcher::PointPos posId2 = cstr.PosId;
Gui::cmdAppObjectArgs(
sketchgui->getObject(),
"addConstraint(Sketcher.Constraint('Symmetric',%d,1,%d,2,%d,%d)) ",
-1
View File
@@ -31,7 +31,6 @@
using namespace Start;
FC_LOG_LEVEL_INIT(ExamplesModel)
ExamplesModel::ExamplesModel(QObject* parent)
: DisplayedFilesModel(parent)
+1
View File
@@ -115,6 +115,7 @@ PyObject* initModule()
PyMOD_INIT_FUNC(StartGui)
{
static StartGui::StartLauncher* launcher = new StartGui::StartLauncher();
Q_UNUSED(launcher)
Base::Console().Log("Loading GUI of Start module... ");
PyObject* mod = StartGui::initModule();
+1
View File
@@ -24,6 +24,7 @@
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
${Boost_INCLUDE_DIRS}
${COIN3D_INCLUDE_DIRS}
${PYTHON_INCLUDE_DIRS}
${QtCore_INCLUDE_DIRS}
${QtSvg_INCLUDE_DIRS}
+1 -1
View File
@@ -25,7 +25,7 @@
#define FREECAD_START_GENERALSETTINGSWIDGET_H
#include <QWidget>
#include <3rdParty/GSL/include/gsl/pointers>
#include <gsl/pointers>
class QLabel;
class QComboBox;
+1 -1
View File
@@ -31,7 +31,7 @@
#endif
#include "ThemeSelectorWidget.h"
#include <3rdParty/GSL/include/gsl/pointers>
#include <gsl/pointers>
#include <App/Application.h>
#include <Gui/Application.h>
#include <Gui/PreferencePackManager.h>
+38 -21
View File
@@ -100,6 +100,32 @@ DrawPage* DrawTemplate::getParentPage() const
return page;
}
// Return the counts related to pages, namely collated page index and total page count
std::pair<int, int> DrawTemplate::getPageNumbers() const
{
std::vector<DocumentObject *> pages = getDocument()->getObjectsOfType(TechDraw::DrawPage::getClassTypeId());
std::vector<QString> pageNames;
for (auto page : pages) {
if (page->isAttachedToDocument() &&
!page->testStatus(App::ObjectStatus::Remove)) {
pageNames.push_back(QString::fromUtf8(page->Label.getValue()));
}
}
QCollator collator;
std::sort(pageNames.begin(), pageNames.end(), collator);
int pos = 0;
DrawPage *page = getParentPage();
if (page) {
auto it = std::find(pageNames.begin(), pageNames.end(), QString::fromUtf8(page->Label.getValue()));
if (it != pageNames.end()) {
pos = it - pageNames.begin() + 1;
}
}
return std::pair<int, int>(pos, (int) pageNames.size());
}
QString DrawTemplate::getAutofillValue(const QString &id) const
{
// author
@@ -133,32 +159,23 @@ QString DrawTemplate::getAutofillValue(const QString &id) const
}
// sheet
else if (id.compare(QString::fromUtf8(Autofill::Sheet)) == 0) {
std::vector<DocumentObject *> pages = getDocument()->getObjectsOfType(TechDraw::DrawPage::getClassTypeId());
std::vector<QString> pageNames;
for (auto page : pages) {
if (page->isAttachedToDocument() &&
!page->testStatus(App::ObjectStatus::Remove)) {
pageNames.push_back(QString::fromUtf8(page->Label.getValue()));
}
}
QCollator collator;
std::sort(pageNames.begin(), pageNames.end(), collator);
int pos = 0;
DrawPage *page = getParentPage();
if (page) {
auto it = std::find(pageNames.begin(), pageNames.end(), QString::fromUtf8(page->Label.getValue()));
if (it != pageNames.end()) {
pos = it - pageNames.begin() + 1;
}
}
return QString::asprintf("%d / %d", pos, (int) pageNames.size());
std::pair<int, int> pageNumbers = getPageNumbers();
return QString::asprintf("%d / %d", pageNumbers.first, pageNumbers.second);
}
// title
else if (id.compare(QString::fromUtf8(Autofill::Title)) == 0) {
return QString::fromUtf8(getDocument()->Label.getValue());
}
// page number
else if (id.compare(QString::fromUtf8(Autofill::PageNumber)) == 0) {
std::pair<int, int> pageNumbers = getPageNumbers();
return QString::number(pageNumbers.first);
}
// page total
else if (id.compare(QString::fromUtf8(Autofill::PageCount)) == 0) {
std::pair<int, int> pageNumbers = getPageNumbers();
return QString::number(pageNumbers.second);
}
return QString();
}
+3
View File
@@ -56,6 +56,7 @@ public:
virtual double getHeight() const;
virtual DrawPage* getParentPage() const;
virtual std::pair<int, int> getPageNumbers() const;
virtual QString getAutofillValue(const QString &id) const;
@@ -76,6 +77,8 @@ public:
static constexpr const char *Scale = "scale";
static constexpr const char *Sheet = "sheet";
static constexpr const char *Title = "title";
static constexpr const char *PageNumber = "page_number";
static constexpr const char *PageCount = "page_count";
};
private:
+1 -1
View File
@@ -178,7 +178,7 @@ int Preferences::balloonShape()
QString Preferences::defaultTemplate()
{
std::string defaultDir = App::Application::getResourceDir() + "Mod/TechDraw/Templates/";
std::string defaultFileName = defaultDir + "A4_LandscapeTD.svg";
std::string defaultFileName = defaultDir + "Default_Template_A4_Landscape.svg";
std::string prefFileName = getPreferenceGroup("Files")->GetASCII("TemplateFile", defaultFileName.c_str());
if (prefFileName.empty()) {
prefFileName = defaultFileName;
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="svg2" width="297mm" height="210mm" version="1.1" viewBox="0 0 297 210" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<metadata id="metadata8">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
</cc:Work>
</rdf:RDF>
</metadata>
</svg>

After

Width:  |  Height:  |  Size: 519 B

+105
View File
@@ -0,0 +1,105 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2024 [email protected] *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD 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 *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
import sys
import unittest
import FreeCAD as App
import Part
try:
from guppy import hpy
Memtest = True
except ImportError:
Memtest = False
try:
import cProfile
Pyprofile = True
except ImportError:
Pyprofile = False
class PerfTestCase(unittest.TestCase):
"""
Special Test Case that takes a list of filenames after the "--pass" parameter to FreeCAD, and
runs a performance test by opening them, starting instrumentation, calling recompute(), and
then saving results.
Intended to be run as "<perf profiling> FreeCAD -t TestPerf --pass <modelname>
External perf profiling requires Python 3.12 or better, and a linux platform.
cProfile profiling and guppy memory information can run anywhere.
"""
def setUp(self):
if "--pass" in sys.argv:
self.fileList = sys.argv[sys.argv.index("--pass") + 1 :]
else:
raise FileNotFoundError("Must provide filename parameter(s) via --pass")
if Part.Shape().ElementMapVersion == "":
self.tnp = ""
else:
self.tnp = ".tnp"
if Memtest:
# Use filename of first model with ".mprofile" appended for python memory use info.
self.memfile = open(self.fileList[0] + self.tnp + ".mprofile", "w", encoding="utf-8")
def testAll(self):
if Pyprofile:
# Generate a cProfile file as a python only time profile.
profile = cProfile.Profile()
profile.enable()
try:
# This is Python 3.12 on supported platforms ( linux ) only so that if we are run under
# an external 'perf' command, we report the python data. This can be extremely useful,
# because it contains not only time consumed, but python and c++ calls that took place
# so deep analysis can be performed on the resulting file. See calling script in
# tools/profile/perftest.sh for a wrapper.
sys.activate_stack_trampoline("perf")
except AttributeError:
pass # Totally okay if we don't have that, we can use the cProfile if it's there.
# Walk all files after the --pass. Normally one to avoid result intermingling.
for fileName in self.fileList:
doc = App.openDocument(fileName)
doc.recompute() # The heart of the performance measurement.
if Memtest:
# If guppy is available, take a heap snapshot and save it. Note that if multiple
# files are provided then their heap data sets will be appended to the same file.
dumpdata = hpy().heap()
dumpdata.stat.dump(self.memfile)
self.memfile.flush()
App.closeDocument(doc.Name)
try:
sys.deactivate_stack_trampoline()
except AttributeError:
pass
if Pyprofile:
profile.disable()
# Use filename of first model with ".cprofile" appended for python profiling information.
profile.dump_stats(self.fileList[0] + self.tnp + ".cprofile")
if Memtest:
self.memfile.close()
+5 -5
View File
@@ -15,7 +15,7 @@
using namespace App;
using namespace Data;
class DocumentObserverTest: public ::testing::Test
class DISABLED_DocumentObserverTest: public ::testing::Test
{
protected:
static void SetUpTestSuite()
@@ -40,7 +40,7 @@ protected:
// NOLINTEND(cppcoreguidelines-non-private-member-variables-in-classes)
};
TEST_F(DocumentObserverTest, hasSubObject)
TEST_F(DISABLED_DocumentObserverTest, hasSubObject)
{
// Arrange
@@ -83,7 +83,7 @@ TEST_F(DocumentObserverTest, hasSubObject)
EXPECT_TRUE(hasSubObj);
}
TEST_F(DocumentObserverTest, hasSubElement)
TEST_F(DISABLED_DocumentObserverTest, hasSubElement)
{
// Arrange
@@ -127,7 +127,7 @@ TEST_F(DocumentObserverTest, hasSubElement)
EXPECT_TRUE(hasSubEl);
}
TEST_F(DocumentObserverTest, normalize)
TEST_F(DISABLED_DocumentObserverTest, normalize)
{
// Arrange
@@ -307,7 +307,7 @@ TEST_F(DocumentObserverTest, normalize)
EXPECT_TRUE(normalizeConvertIndex);
}
TEST_F(DocumentObserverTest, normalized)
TEST_F(DISABLED_DocumentObserverTest, normalized)
{
// Arrange
@@ -1803,6 +1803,7 @@ TEST_F(TopoShapeExpansionTest, makeElementThickSolid)
std::vector<TopoShape> shapes = {subFaces[0], subFaces[1]};
// Act
TopoShape& result = cube1TS.makeElementThickSolid(cube1TS, shapes, 0.1, 1e-07);
(void)result;
auto elements = elementMap(cube1TS);
// Assert
EXPECT_EQ(cube1TS.countSubElements("Wire"), 16);
@@ -312,11 +312,11 @@ TEST_F(TopoShapeMakeShapeWithElementMapTests, findMakerOpInElementMap)
EXPECT_EQ(tmpShape.getElementMapSize(), 26);
// For all the mappedElements ...
for (const auto& mappedElement : tmpShape.getElementMap()) {
// TODO: This no longer works, it needs a different check. We don't set MAK
// EXPECT_NE(mappedElement.name.find(OpCodes::Maker),
// -1); // ... we check that there's the "MAK" OpCode
}
// for (const auto& mappedElement : tmpShape.getElementMap()) {
// TODO: This no longer works, it needs a different check. We don't set MAK
// EXPECT_NE(mappedElement.name.find(OpCodes::Maker),
// -1); // ... we check that there's the "MAK" OpCode
//}
}
}
+36
View File
@@ -0,0 +1,36 @@
#! /bin/bash
# Example file to drive the performance profiling python test from the shell.
# Built to support the Topological Naming Problem fixes from early 2024;
# this will likely need tweaking for your use.
notnp=<Path to your first executable to profile goes here> #<dir>/bin/FreeCAD${cmd}
tnp=<Path to your second executable to profile goes here> #<dir/bin/FreeCAD${cmd}
results="results.txt" # File to append measurements to. We do not clear so xargs works on script
perf record -o "$1.perf" $notnp -t TestPerf --pass "$@"
perf record -o "$1.tnp.perf" $tnp -t TestPerf --pass "$@"
# For interactive walking of the details using perf:
#perf report -i $1.perf
# After the two test runs above, process the resulting files to get the numbers.
# For perf, use the 'script' command to pull the info out of the file, and then do a little
# old school unix manipulation
times=($(perf script -F time -i "$1.perf" | sed -e's/://' -e'1p;$!d')) # first, last timestamps
totaltime=$(echo ${times[1]} - ${times[0]} | bc)
memory=$(grep .size: "$1.mprofile" | cut -f2 -d\ )
memory2=$(perf script --header -i "$1.perf" | grep "data size" | cut -f2 -d:)
timestnp=($(perf script -F time -i "$1.tnp.perf" | sed -e's/://' -e'1p;$!d')) # first, last times
totaltimetnp=$(echo ${timestnp[1]} - ${timestnp[0]} | bc)
memorytnp=$(grep .size: "$1.tnp.mprofile" | cut -f2 -d\ )
memory2tnp=$(perf script --header -i "$1.tnp.perf" | grep "data size" | cut -f2 -d:)
# To calculate in this script instead of externally, you could do something like this:
# delta=$(echo ${totaltimetnp} - ${totaltime} | bc)
# percent=$(echo "scale=6; ${delta} / ${totaltime} * 100" | bc)
# Summarize the run of one document into a CSV line suitable for importing into a spreadsheet.
echo $totaltime,$totaltimetnp,$memory,$memorytnp,$memory2,$memory2tnp,"$1" >>"${results}"