Merge branch 'main' into bgbsww-toponamingFeatureDressup10399
@@ -41,6 +41,7 @@ body:
|
||||
- FEM
|
||||
- File formats
|
||||
- GCS
|
||||
- Mesh
|
||||
- OpenSCAD
|
||||
- Part
|
||||
- PartDesign
|
||||
|
||||
@@ -3,67 +3,85 @@
|
||||
# - any: ['src/**/*', '!src/docs/*']
|
||||
|
||||
Core:
|
||||
- 'src/App/**/*'
|
||||
- 'src/Base/**/*'
|
||||
- 'src/Gui/**/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/App/**/*', 'src/Base/**/*', 'src/Gui/**/*']
|
||||
|
||||
:octocat::
|
||||
- '.github/**/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['.github/**/*']
|
||||
|
||||
Addon Manager:
|
||||
- 'src/Mod/AddonManager/**/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Mod/AddonManager/**/*']
|
||||
|
||||
Materials:
|
||||
- 'src/Mod/Material/**/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Mod/Material/**/*']
|
||||
|
||||
WB Arch:
|
||||
- 'src/Mod/Arch/**/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Mod/Arch/**/*']
|
||||
|
||||
WB Assembly:
|
||||
- 'src/Mod/Assembly/**/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Mod/Assembly/**/*']
|
||||
|
||||
WB Draft:
|
||||
- 'src/Mod/Draft/**/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Mod/Draft/**/*']
|
||||
|
||||
WB FEM:
|
||||
- 'src/Mod/Fem/**/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Mod/Fem/**/*']
|
||||
|
||||
WB Mesh:
|
||||
- 'src/Mod/Mesh/**/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Mod/Mesh/**/*']
|
||||
|
||||
WB MeshPart:
|
||||
- 'src/Mod/MeshPart/**/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Mod/MeshPart/**/*']
|
||||
|
||||
WB OpenSCAD:
|
||||
- 'src/Mod/OpenSCAD/**/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Mod/OpenSCAD/**/*']
|
||||
|
||||
WB Part:
|
||||
- 'src/Mod/Part/**/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Mod/Part/**/*']
|
||||
|
||||
WB Part Design:
|
||||
- 'src/Mod/PartDesign/**/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Mod/PartDesign/**/*']
|
||||
|
||||
WB CAM:
|
||||
- 'src/Mod/CAM/**/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Mod/CAM/**/*']
|
||||
|
||||
WB Points:
|
||||
- 'src/Mod/Points/**/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Mod/Points/**/*']
|
||||
|
||||
WB ReverseEngineering:
|
||||
- 'src/Mod/ReverseEngineering/**/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Mod/ReverseEngineering/**/*']
|
||||
|
||||
WB Sketcher:
|
||||
- 'src/Mod/Sketcher/**/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Mod/Sketcher/**/*']
|
||||
|
||||
WB Spreadsheet:
|
||||
- 'src/Mod/Spreadsheet/**/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Mod/Spreadsheet/**/*']
|
||||
|
||||
WB Surface:
|
||||
- 'src/Mod/Surface/**/*/'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Mod/Surface/**/*/']
|
||||
|
||||
WB TechDraw:
|
||||
- 'src/Mod/TechDraw/**/*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Mod/TechDraw/**/*']
|
||||
|
||||
WB Test:
|
||||
- 'src/Mod/Test/**/*'
|
||||
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Mod/Test/**/*']
|
||||
|
||||
@@ -1304,6 +1304,24 @@ 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;
|
||||
|
||||
@@ -300,6 +300,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
|
||||
|
||||
@@ -225,6 +225,13 @@ Return tuple(obj,newElementName,oldElementName)
|
||||
<UserDocu>adjustRelativeLinks(parent,recursive=True) -- auto correct potential cyclic dependencies</UserDocu>
|
||||
</Documentation>
|
||||
</Methode>
|
||||
<Methode Name="getElementMapVersion" Const="true">
|
||||
<Documentation>
|
||||
<UserDocu>
|
||||
getElementMapVersion(property_name): return element map version of a given geometry property
|
||||
</UserDocu>
|
||||
</Documentation>
|
||||
</Methode>
|
||||
<Attribute Name="OutList" ReadOnly="true">
|
||||
<Documentation>
|
||||
<UserDocu>A list of all objects this object links to.</UserDocu>
|
||||
|
||||
@@ -745,6 +745,22 @@ PyObject* DocumentObjectPy::getPathsByOutList(PyObject *args)
|
||||
}
|
||||
}
|
||||
|
||||
PyObject* DocumentObjectPy::getElementMapVersion(PyObject* args)
|
||||
{
|
||||
const char* name;
|
||||
PyObject* restored = Py_False;
|
||||
if (!PyArg_ParseTuple(args, "s|O", &name, &restored)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Property* prop = getDocumentObjectPtr()->getPropertyByName(name);
|
||||
if (!prop) {
|
||||
throw Py::ValueError("property not found");
|
||||
}
|
||||
return Py::new_reference_to(
|
||||
Py::String(getDocumentObjectPtr()->getElementMapVersion(prop, Base::asBoolean(restored))));
|
||||
}
|
||||
|
||||
PyObject *DocumentObjectPy::getCustomAttributes(const char* ) const
|
||||
{
|
||||
return nullptr;
|
||||
|
||||
@@ -726,8 +726,8 @@ MappedName ElementMap::dehashElementName(const MappedName& name) const
|
||||
FC_LOG("cannot de-hash id " << id);// NOLINT
|
||||
return name;
|
||||
}
|
||||
MappedName ret(
|
||||
sid.toString());// FIXME .toString() was missing in original function. is this correct?
|
||||
MappedName ret(sid);
|
||||
// sid.toString());// FIXME .toString() was missing in original function. is this correct?
|
||||
FC_TRACE("de-hash " << name << " -> " << ret);// NOLINT
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -58,6 +58,12 @@ If an object has no such property then None is returned.
|
||||
Unlike to getPropertyNameOfGeometry this function returns the geometry, not its name.</UserDocu>
|
||||
</Documentation>
|
||||
</Methode>
|
||||
<Attribute Name="ElementMapVersion" ReadOnly="true">
|
||||
<Documentation>
|
||||
<UserDocu>Element map version</UserDocu>
|
||||
</Documentation>
|
||||
<Parameter Name="ElementMapVersion" Type="String" />
|
||||
</Attribute>
|
||||
<CustomAttributes />
|
||||
</PythonExport>
|
||||
</GenerateModel>
|
||||
|
||||
@@ -93,3 +93,8 @@ int GeoFeaturePy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
Py::String GeoFeaturePy::getElementMapVersion() const {
|
||||
return Py::String(getGeoFeaturePtr()->getElementMapVersion(
|
||||
getGeoFeaturePtr()->getPropertyOfGeometry()));
|
||||
}
|
||||
|
||||
@@ -171,9 +171,7 @@ inline bool DOMTreeErrorReporter::getSawErrors() const
|
||||
ParameterGrp::ParameterGrp(XERCES_CPP_NAMESPACE_QUALIFIER DOMElement* GroupNode,
|
||||
const char* sName,
|
||||
ParameterGrp* Parent)
|
||||
: Base::Handled()
|
||||
, Subject<const char*>()
|
||||
, _pGroupNode(GroupNode)
|
||||
: _pGroupNode(GroupNode)
|
||||
, _Parent(Parent)
|
||||
{
|
||||
if (sName) {
|
||||
@@ -202,7 +200,7 @@ ParameterGrp::~ParameterGrp()
|
||||
//**************************************************************************
|
||||
// Access methods
|
||||
|
||||
void ParameterGrp::copyTo(Base::Reference<ParameterGrp> Grp)
|
||||
void ParameterGrp::copyTo(const Base::Reference<ParameterGrp>& Grp)
|
||||
{
|
||||
if (Grp == this) {
|
||||
return;
|
||||
@@ -215,7 +213,7 @@ void ParameterGrp::copyTo(Base::Reference<ParameterGrp> Grp)
|
||||
insertTo(Grp);
|
||||
}
|
||||
|
||||
void ParameterGrp::insertTo(Base::Reference<ParameterGrp> Grp)
|
||||
void ParameterGrp::insertTo(const Base::Reference<ParameterGrp>& Grp)
|
||||
{
|
||||
if (Grp == this) {
|
||||
return;
|
||||
@@ -309,7 +307,7 @@ void ParameterGrp::revert(const char* FileName)
|
||||
revert(Base::Reference<ParameterGrp>(Mngr));
|
||||
}
|
||||
|
||||
void ParameterGrp::revert(Base::Reference<ParameterGrp> Grp)
|
||||
void ParameterGrp::revert(const Base::Reference<ParameterGrp>& Grp)
|
||||
{
|
||||
if (Grp == this) {
|
||||
return;
|
||||
@@ -869,8 +867,10 @@ unsigned long ParameterGrp::GetUnsigned(const char* Name, unsigned long lPreset)
|
||||
if (!pcElem) {
|
||||
return lPreset;
|
||||
}
|
||||
|
||||
// if yes check the value and return
|
||||
return strtoul(StrX(pcElem->getAttribute(XStr("Value").unicodeForm())).c_str(), nullptr, 10);
|
||||
const int base = 10;
|
||||
return strtoul(StrX(pcElem->getAttribute(XStr("Value").unicodeForm())).c_str(), nullptr, base);
|
||||
}
|
||||
|
||||
void ParameterGrp::SetUnsigned(const char* Name, unsigned long lValue)
|
||||
@@ -887,6 +887,7 @@ std::vector<unsigned long> ParameterGrp::GetUnsigneds(const char* sFilter) const
|
||||
}
|
||||
|
||||
std::string Name;
|
||||
const int base = 10;
|
||||
|
||||
DOMElement* pcTemp = FindElement(_pGroupNode, "FCUInt");
|
||||
while (pcTemp) {
|
||||
@@ -896,7 +897,7 @@ std::vector<unsigned long> ParameterGrp::GetUnsigneds(const char* sFilter) const
|
||||
vrValues.push_back(
|
||||
strtoul(StrX(pcTemp->getAttribute(XStr("Value").unicodeForm())).c_str(),
|
||||
nullptr,
|
||||
10));
|
||||
base));
|
||||
}
|
||||
pcTemp = FindNextElement(pcTemp, "FCUInt");
|
||||
}
|
||||
@@ -913,6 +914,7 @@ ParameterGrp::GetUnsignedMap(const char* sFilter) const
|
||||
}
|
||||
|
||||
std::string Name;
|
||||
const int base = 10;
|
||||
|
||||
DOMElement* pcTemp = FindElement(_pGroupNode, "FCUInt");
|
||||
while (pcTemp) {
|
||||
@@ -923,7 +925,7 @@ ParameterGrp::GetUnsignedMap(const char* sFilter) const
|
||||
Name,
|
||||
(strtoul(StrX(pcTemp->getAttribute(XStr("Value").unicodeForm())).c_str(),
|
||||
nullptr,
|
||||
10)));
|
||||
base)));
|
||||
}
|
||||
pcTemp = FindNextElement(pcTemp, "FCUInt");
|
||||
}
|
||||
@@ -1398,11 +1400,11 @@ ParameterGrp::FindElement(XERCES_CPP_NAMESPACE_QUALIFIER DOMElement* Start,
|
||||
if (Name) {
|
||||
DOMNode* attr = FindAttribute(clChild, "Name");
|
||||
if (attr && !strcmp(Name, StrX(attr->getNodeValue()).c_str())) {
|
||||
return static_cast<DOMElement*>(clChild);
|
||||
return dynamic_cast<DOMElement*>(clChild);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return static_cast<DOMElement*>(clChild);
|
||||
return dynamic_cast<DOMElement*>(clChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1423,7 +1425,7 @@ ParameterGrp::FindNextElement(XERCES_CPP_NAMESPACE_QUALIFIER DOMNode* Prev, cons
|
||||
if (clChild->getNodeType() == DOMNode::ELEMENT_NODE) {
|
||||
// the right node Type
|
||||
if (!strcmp(Type, StrX(clChild->getNodeName()).c_str())) {
|
||||
return static_cast<DOMElement*>(clChild);
|
||||
return dynamic_cast<DOMElement*>(clChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1529,8 +1531,8 @@ void ParameterGrp::_Reset()
|
||||
//**************************************************************************
|
||||
// ParameterSerializer
|
||||
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
ParameterSerializer::ParameterSerializer(const std::string& fn)
|
||||
: filename(fn)
|
||||
ParameterSerializer::ParameterSerializer(std::string fn)
|
||||
: filename(std::move(fn))
|
||||
{}
|
||||
|
||||
ParameterSerializer::~ParameterSerializer() = default;
|
||||
@@ -1555,7 +1557,7 @@ bool ParameterSerializer::LoadOrCreateDocument(ParameterManager& mgr)
|
||||
// ParameterManager
|
||||
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
static XercesDOMParser::ValSchemes gValScheme = XercesDOMParser::Val_Auto;
|
||||
static XercesDOMParser::ValSchemes gValScheme = XercesDOMParser::Val_Auto; // NOLINT
|
||||
|
||||
//**************************************************************************
|
||||
// Construction/Destruction
|
||||
@@ -1610,6 +1612,7 @@ ParameterManager::ParameterManager()
|
||||
//
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// NOLINTBEGIN
|
||||
gDoNamespaces = false;
|
||||
gDoSchema = false;
|
||||
gSchemaFullChecking = false;
|
||||
@@ -1622,6 +1625,7 @@ ParameterManager::ParameterManager()
|
||||
gDiscardDefaultContent = true;
|
||||
gUseFilter = true;
|
||||
gFormatPrettyPrint = true;
|
||||
// NOLINTEND
|
||||
}
|
||||
|
||||
/** Destruction
|
||||
@@ -1736,8 +1740,9 @@ int ParameterManager::LoadDocument(const char* sFileName)
|
||||
|
||||
try {
|
||||
#if defined(FC_OS_WIN32)
|
||||
LocalFileInputSource inputSource(
|
||||
reinterpret_cast<const XMLCh*>(file.toStdWString().c_str()));
|
||||
std::wstring name = file.toStdWString();
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
LocalFileInputSource inputSource(reinterpret_cast<const XMLCh*>(name.c_str()));
|
||||
#else
|
||||
LocalFileInputSource inputSource(XStr(file.filePath().c_str()).unicodeForm());
|
||||
#endif
|
||||
@@ -1760,14 +1765,14 @@ int ParameterManager::LoadDocument(const XERCES_CPP_NAMESPACE_QUALIFIER InputSou
|
||||
// The parser will call back to methods of the ErrorHandler if it
|
||||
// discovers errors during the course of parsing the XML document.
|
||||
//
|
||||
XercesDOMParser* parser = new XercesDOMParser;
|
||||
auto parser = new XercesDOMParser;
|
||||
parser->setValidationScheme(gValScheme);
|
||||
parser->setDoNamespaces(gDoNamespaces);
|
||||
parser->setDoSchema(gDoSchema);
|
||||
parser->setValidationSchemaFullChecking(gSchemaFullChecking);
|
||||
parser->setCreateEntityReferenceNodes(gDoCreate);
|
||||
|
||||
DOMTreeErrorReporter* errReporter = new DOMTreeErrorReporter();
|
||||
auto errReporter = new DOMTreeErrorReporter();
|
||||
parser->setErrorHandler(errReporter);
|
||||
|
||||
//
|
||||
@@ -1778,19 +1783,16 @@ int ParameterManager::LoadDocument(const XERCES_CPP_NAMESPACE_QUALIFIER InputSou
|
||||
try {
|
||||
parser->parse(inputSource);
|
||||
}
|
||||
|
||||
catch (const XMLException& e) {
|
||||
std::cerr << "An error occurred during parsing\n Message: " << StrX(e.getMessage())
|
||||
<< std::endl;
|
||||
errorsOccured = true;
|
||||
}
|
||||
|
||||
catch (const DOMException& e) {
|
||||
std::cerr << "A DOM error occurred during parsing\n DOMException code: " << e.code
|
||||
<< std::endl;
|
||||
errorsOccured = true;
|
||||
}
|
||||
|
||||
catch (...) {
|
||||
std::cerr << "An error occurred during parsing\n " << std::endl;
|
||||
errorsOccured = true;
|
||||
@@ -1836,11 +1838,13 @@ void ParameterManager::SaveDocument(const char* sFileName) const
|
||||
// LocalFileFormatTarget prints the resultant XML stream
|
||||
// to a file once it receives any thing from the serializer.
|
||||
//
|
||||
XMLFormatTarget* myFormTarget {};
|
||||
#if defined(FC_OS_WIN32)
|
||||
XMLFormatTarget* myFormTarget =
|
||||
new LocalFileFormatTarget(reinterpret_cast<const XMLCh*>(file.toStdWString().c_str()));
|
||||
std::wstring name = file.toStdWString();
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
myFormTarget = new LocalFileFormatTarget(reinterpret_cast<const XMLCh*>(name.c_str()));
|
||||
#else
|
||||
XMLFormatTarget* myFormTarget = new LocalFileFormatTarget(file.filePath().c_str());
|
||||
myFormTarget = new LocalFileFormatTarget(file.filePath().c_str());
|
||||
#endif
|
||||
SaveDocument(myFormTarget);
|
||||
delete myFormTarget;
|
||||
@@ -1857,12 +1861,14 @@ void ParameterManager::SaveDocument(XMLFormatTarget* pFormatTarget) const
|
||||
std::unique_ptr<DOMPrintFilter> myFilter;
|
||||
std::unique_ptr<DOMErrorHandler> myErrorHandler;
|
||||
|
||||
// NOLINTBEGIN
|
||||
// get a serializer, an instance of DOMWriter
|
||||
XMLCh tempStr[100];
|
||||
XMLString::transcode("LS", tempStr, 99);
|
||||
DOMImplementation* impl = DOMImplementationRegistry::getDOMImplementation(tempStr);
|
||||
DOMLSSerializer* theSerializer =
|
||||
static_cast<DOMImplementationLS*>(impl)->createLSSerializer();
|
||||
// NOLINTEND
|
||||
|
||||
// set user specified end of line sequence and output encoding
|
||||
theSerializer->setNewLine(gMyEOLSequence);
|
||||
@@ -1885,6 +1891,8 @@ void ParameterManager::SaveDocument(XMLFormatTarget* pFormatTarget) const
|
||||
// plug in user's own error handler
|
||||
myErrorHandler = std::make_unique<DOMPrintErrorHandler>();
|
||||
DOMConfiguration* config = theSerializer->getDomConfig();
|
||||
|
||||
// NOLINTBEGIN
|
||||
config->setParameter(XMLUni::fgDOMErrorHandler, myErrorHandler.get());
|
||||
|
||||
// set feature if the serializer supports the feature/mode
|
||||
@@ -1900,6 +1908,7 @@ void ParameterManager::SaveDocument(XMLFormatTarget* pFormatTarget) const
|
||||
if (config->canSetParameter(XMLUni::fgDOMWRTFormatPrettyPrint, gFormatPrettyPrint)) {
|
||||
config->setParameter(XMLUni::fgDOMWRTFormatPrettyPrint, gFormatPrettyPrint);
|
||||
}
|
||||
// NOLINTEND
|
||||
|
||||
theOutput->setByteStream(pFormatTarget);
|
||||
theSerializer->write(_pDocument, theOutput);
|
||||
@@ -1955,7 +1964,8 @@ void ParameterManager::CheckDocument() const
|
||||
|
||||
// Either load the XSD file from disk or use the built-in string
|
||||
// const char* xsdFile = "...";
|
||||
std::string xsdStr(xmlSchemeString);
|
||||
std::string xsdStr(xmlSchemeString); // NOLINT
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
MemBufInputSource xsdFile(reinterpret_cast<const XMLByte*>(xsdStr.c_str()),
|
||||
xsdStr.size(),
|
||||
"Parameter.xsd");
|
||||
|
||||
@@ -102,9 +102,9 @@ public:
|
||||
/** @name copy and insertation */
|
||||
//@{
|
||||
/// make a deep copy to the other group
|
||||
void copyTo(Base::Reference<ParameterGrp>);
|
||||
void copyTo(const Base::Reference<ParameterGrp>&);
|
||||
/// overwrite everything similar, leave the others alone
|
||||
void insertTo(Base::Reference<ParameterGrp>);
|
||||
void insertTo(const Base::Reference<ParameterGrp>&);
|
||||
/// export this group to a file
|
||||
void exportTo(const char* FileName);
|
||||
/// import from a file to this group
|
||||
@@ -114,7 +114,7 @@ public:
|
||||
/// revert to default value by deleting any parameter that has the same value in the given file
|
||||
void revert(const char* FileName);
|
||||
/// revert to default value by deleting any parameter that has the same value in the given group
|
||||
void revert(Base::Reference<ParameterGrp>);
|
||||
void revert(const Base::Reference<ParameterGrp>&);
|
||||
//@}
|
||||
|
||||
/** @name methods for group handling */
|
||||
@@ -351,7 +351,7 @@ protected:
|
||||
class BaseExport ParameterSerializer
|
||||
{
|
||||
public:
|
||||
explicit ParameterSerializer(const std::string& fn);
|
||||
explicit ParameterSerializer(std::string fn);
|
||||
ParameterSerializer(const ParameterSerializer&) = delete;
|
||||
ParameterSerializer(ParameterSerializer&&) = delete;
|
||||
virtual ~ParameterSerializer();
|
||||
|
||||
@@ -1462,6 +1462,7 @@ bool PythonCommand::isCheckable() const
|
||||
|
||||
bool PythonCommand::isChecked() const
|
||||
{
|
||||
Base::PyGILStateLocker lock;
|
||||
PyObject* item = PyDict_GetItemString(_pcPyResourceDict,"Checkable");
|
||||
if (!item) {
|
||||
throw Base::ValueError("PythonCommand::isChecked(): Method GetResources() of the Python "
|
||||
|
||||
@@ -149,7 +149,7 @@ StdCmdToggleFreeze::StdCmdToggleFreeze()
|
||||
sGroup = "File";
|
||||
sMenuText = QT_TR_NOOP("Toggle freeze");
|
||||
static std::string toolTip = std::string("<p>")
|
||||
+ QT_TR_NOOP("Toggles freeze state of the selected objects. A freezed object is not recomputed when its parents change.")
|
||||
+ QT_TR_NOOP("Toggles freeze state of the selected objects. A frozen object is not recomputed when its parents change.")
|
||||
+ "</p>";
|
||||
sToolTipText = toolTip.c_str();
|
||||
sStatusTip = sToolTipText;
|
||||
|
||||
@@ -484,7 +484,8 @@ void ExtrusionHelper::createTaperedPrismOffset(TopoDS_Wire sourceWire,
|
||||
|
||||
void ExtrusionHelper::makeElementDraft(const ExtrusionParameters& params,
|
||||
const TopoShape& _shape,
|
||||
std::vector<TopoShape>& drafts)
|
||||
std::vector<TopoShape>& drafts,
|
||||
App::StringHasherRef hasher)
|
||||
{
|
||||
double distanceFwd = tan(params.taperAngleFwd) * params.lengthFwd;
|
||||
double distanceRev = tan(params.taperAngleRev) * params.lengthRev;
|
||||
@@ -518,7 +519,7 @@ void ExtrusionHelper::makeElementDraft(const ExtrusionParameters& params,
|
||||
}
|
||||
else {
|
||||
unsigned pos = drafts.size();
|
||||
makeElementDraft(params, outerWire, drafts);
|
||||
makeElementDraft(params, outerWire, drafts, hasher);
|
||||
if (drafts.size() != pos + 1) {
|
||||
Standard_Failure::Raise("Failed to make drafted extrusion");
|
||||
}
|
||||
@@ -528,7 +529,7 @@ void ExtrusionHelper::makeElementDraft(const ExtrusionParameters& params,
|
||||
wires,
|
||||
"",
|
||||
TopoShape::SingleShapeCompoundCreationPolicy::returnShape);
|
||||
makeElementDraft(params, innerWires, inner);
|
||||
makeElementDraft(params, innerWires, inner, hasher);
|
||||
if (inner.empty()) {
|
||||
Standard_Failure::Raise("Failed to make drafted extrusion with inner hole");
|
||||
}
|
||||
@@ -550,7 +551,7 @@ void ExtrusionHelper::makeElementDraft(const ExtrusionParameters& params,
|
||||
}
|
||||
else if (shape.shapeType() == TopAbs_COMPOUND) {
|
||||
for (auto& s : shape.getSubTopoShapes()) {
|
||||
makeElementDraft(params, s, drafts);
|
||||
makeElementDraft(params, s, drafts, hasher);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -598,7 +599,7 @@ void ExtrusionHelper::makeElementDraft(const ExtrusionParameters& params,
|
||||
}
|
||||
|
||||
mkGenerator.Build();
|
||||
drafts.push_back(TopoShape(0).makeElementShape(mkGenerator, list_of_sections));
|
||||
drafts.push_back(TopoShape(0, hasher).makeElementShape(mkGenerator, list_of_sections));
|
||||
}
|
||||
catch (Standard_Failure&) {
|
||||
throw;
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <TopoDS_Shape.hxx>
|
||||
|
||||
#include <Mod/Part/PartGlobal.h>
|
||||
#include "TopoShape.h"
|
||||
|
||||
|
||||
namespace Part
|
||||
@@ -88,8 +89,10 @@ public:
|
||||
TopoDS_Wire& result);
|
||||
/** Same as makeDraft() with support of element mapping
|
||||
*/
|
||||
static void
|
||||
makeElementDraft(const ExtrusionParameters& params, const TopoShape&, std::vector<TopoShape>&);
|
||||
static void makeElementDraft(const ExtrusionParameters& params,
|
||||
const TopoShape&,
|
||||
std::vector<TopoShape>&,
|
||||
App::StringHasherRef hasher);
|
||||
};
|
||||
|
||||
} //namespace Part
|
||||
|
||||
@@ -61,9 +61,12 @@ void Part::FaceMaker::addTopoShape(const TopoShape& shape) {
|
||||
break;
|
||||
case TopAbs_WIRE:
|
||||
this->myWires.push_back(TopoDS::Wire(sh));
|
||||
this->myTopoWires.push_back(shape);
|
||||
break;
|
||||
case TopAbs_EDGE:
|
||||
this->myWires.push_back(BRepBuilderAPI_MakeWire(TopoDS::Edge(sh)).Wire());
|
||||
this->myTopoWires.push_back(shape);
|
||||
this->myTopoWires.back().setShape(this->myWires.back(), false);
|
||||
break;
|
||||
case TopAbs_FACE:
|
||||
this->myInputFaces.push_back(sh);
|
||||
@@ -186,6 +189,7 @@ void Part::FaceMaker::postBuild() {
|
||||
if(!op)
|
||||
op = Part::OpCodes::Face;
|
||||
const auto &faces = this->myTopoShape.getSubTopoShapes(TopAbs_FACE);
|
||||
std::set<Data::MappedName> namesUsed;
|
||||
// name the face using the edges of its outer wire
|
||||
for(auto &face : faces) {
|
||||
++index;
|
||||
@@ -208,10 +212,25 @@ void Part::FaceMaker::postBuild() {
|
||||
|
||||
std::vector<Data::MappedName> names;
|
||||
Data::ElementIDRefs sids;
|
||||
#ifdef FC_USE_TNP_FIX
|
||||
// To avoid name collision, we keep track of any used names to make sure
|
||||
// to use at least 'minElementNames' number of unused element names to
|
||||
// generate the face name.
|
||||
int nameCount = 0;
|
||||
for (const auto &e : edgeNames) {
|
||||
names.push_back(e.name);
|
||||
sids += e.sids;
|
||||
if (namesUsed.insert(e.name).second) {
|
||||
if (++nameCount >= minElementNames)
|
||||
break;
|
||||
}
|
||||
}
|
||||
#else
|
||||
// We just use the first source element name to make the face name more
|
||||
// stable
|
||||
names.push_back(edgeNames.begin()->name);
|
||||
sids = edgeNames.begin()->sids;
|
||||
#endif
|
||||
this->myTopoShape.setElementComboName(
|
||||
Data::IndexedName::fromConst("Face",index),names,op,nullptr,&sids);
|
||||
}
|
||||
|
||||
@@ -107,10 +107,12 @@ public:
|
||||
protected:
|
||||
std::vector<TopoShape> mySourceShapes; //wire or compound
|
||||
std::vector<TopoDS_Wire> myWires; //wires from mySourceShapes
|
||||
std::vector<TopoShape> myTopoWires;
|
||||
std::vector<TopoDS_Compound> myCompounds; //compounds, for recursive processing
|
||||
std::vector<TopoDS_Shape> myShapesToReturn;
|
||||
std::vector<TopoDS_Shape> myInputFaces;
|
||||
TopoShape myTopoShape;
|
||||
int minElementNames = 1;
|
||||
|
||||
/**
|
||||
* @brief Build_Essence: build routine that can assume there is no nesting.
|
||||
|
||||
@@ -320,7 +320,7 @@ void Extrusion::extrudeShape(TopoShape &result, const TopoShape &source, const E
|
||||
Base::SignalException se;
|
||||
#endif
|
||||
std::vector<TopoShape> drafts;
|
||||
ExtrusionHelper::makeElementDraft(params, myShape, drafts);
|
||||
ExtrusionHelper::makeElementDraft(params, myShape, drafts, result.Hasher);
|
||||
if (drafts.empty()) {
|
||||
Standard_Failure::Raise("Drafting shape failed");
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <App/Link.h>
|
||||
|
||||
#include "FeatureOffset.h"
|
||||
#include <App/Document.h>
|
||||
|
||||
|
||||
using namespace Part;
|
||||
@@ -96,7 +97,7 @@ App::DocumentObjectExecReturn *Offset::execute()
|
||||
if(shape.isNull())
|
||||
return new App::DocumentObjectExecReturn("Invalid source link");
|
||||
auto join = static_cast<JoinType>(Join.getValue());
|
||||
this->Shape.setValue(TopoShape(0).makeElementOffset(
|
||||
this->Shape.setValue(TopoShape(0, getDocument()->getStringHasher()).makeElementOffset(
|
||||
shape,offset,tol,inter,self,mode,join,fill ? FillType::fill : FillType::noFill));
|
||||
#endif
|
||||
return App::DocumentObject::StdReturn;
|
||||
|
||||
@@ -1319,6 +1319,7 @@ template<> PyObject* Part::FeaturePython::getPyObject() {
|
||||
template class PartExport FeaturePythonT<Part::Feature>;
|
||||
}
|
||||
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
std::vector<Part::cutFaces> Part::findAllFacesCutBy(
|
||||
const TopoDS_Shape& shape, const TopoDS_Shape& face, const gp_Dir& dir)
|
||||
{
|
||||
@@ -1359,6 +1360,51 @@ std::vector<Part::cutFaces> Part::findAllFacesCutBy(
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Part::cutTopoShapeFaces>
|
||||
Part::findAllFacesCutBy(const TopoShape& shape, const TopoShape& face, const gp_Dir& dir)
|
||||
{
|
||||
// Find the centre of gravity of the face
|
||||
GProp_GProps props;
|
||||
BRepGProp::SurfaceProperties(face.getShape(), props);
|
||||
gp_Pnt cog = props.CentreOfMass();
|
||||
|
||||
// create a line through the centre of gravity
|
||||
gp_Lin line = gce_MakeLin(cog, dir);
|
||||
|
||||
// Find intersection of line with all faces of the shape
|
||||
std::vector<cutTopoShapeFaces> result;
|
||||
BRepIntCurveSurface_Inter mkSection;
|
||||
// TODO: Less precision than Confusion() should be OK?
|
||||
|
||||
for (mkSection.Init(shape.getShape(), line, Precision::Confusion()); mkSection.More();
|
||||
mkSection.Next()) {
|
||||
gp_Pnt iPnt = mkSection.Pnt();
|
||||
double dsq = cog.SquareDistance(iPnt);
|
||||
|
||||
if (dsq < Precision::Confusion()) {
|
||||
continue; // intersection with original face
|
||||
}
|
||||
|
||||
// Find out which side of the original face the intersection is on
|
||||
gce_MakeDir mkDir(cog, iPnt);
|
||||
if (!mkDir.IsDone()) {
|
||||
continue; // some error (appears highly unlikely to happen, though...)
|
||||
}
|
||||
|
||||
if (mkDir.Value().IsOpposite(dir, Precision::Confusion())) {
|
||||
continue; // wrong side of face (opposite to extrusion direction)
|
||||
}
|
||||
|
||||
cutTopoShapeFaces newF;
|
||||
newF.face = mkSection.Face();
|
||||
newF.face.mapSubElement(shape);
|
||||
newF.distsq = dsq;
|
||||
result.push_back(newF);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Part::checkIntersection(const TopoDS_Shape& first, const TopoDS_Shape& second,
|
||||
const bool quick, const bool touch_is_intersection) {
|
||||
|
||||
|
||||
@@ -211,14 +211,24 @@ public:
|
||||
* Find all faces cut by a line through the centre of gravity of a given face
|
||||
* Useful for the "up to face" options to pocket or pad
|
||||
*/
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
struct cutFaces {
|
||||
TopoDS_Face face;
|
||||
double distsq;
|
||||
};
|
||||
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
PartExport
|
||||
std::vector<cutFaces> findAllFacesCutBy(const TopoDS_Shape& shape,
|
||||
const TopoDS_Shape& face, const gp_Dir& dir);
|
||||
struct cutTopoShapeFaces
|
||||
{
|
||||
TopoShape face;
|
||||
double distsq;
|
||||
};
|
||||
|
||||
PartExport std::vector<cutTopoShapeFaces>
|
||||
findAllFacesCutBy(const TopoShape& shape, const TopoShape& face, const gp_Dir& dir);
|
||||
|
||||
/**
|
||||
* Check for intersection between the two shapes. Only solids are guaranteed to work properly
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
|
||||
#include <App/Link.h>
|
||||
|
||||
#include <App/Document.h>
|
||||
#include "PartFeatures.h"
|
||||
#include "TopoShapeOpCode.h"
|
||||
|
||||
@@ -837,7 +838,7 @@ App::DocumentObjectExecReturn* Thickness::execute()
|
||||
short join = (short)Join.getValue();
|
||||
|
||||
#ifdef FC_USE_TNP_FIX
|
||||
this->Shape.setValue(TopoShape(0)
|
||||
this->Shape.setValue(TopoShape(0,getDocument()->getStringHasher())
|
||||
.makeElementThickSolid(base,
|
||||
shapes,
|
||||
thickness,
|
||||
|
||||
@@ -3957,8 +3957,130 @@ TopoShape &TopoShape::makeRefine(const TopoShape &shape, const char *op, RefineF
|
||||
return *this;
|
||||
}
|
||||
|
||||
// TODO: Does the toponaming branch version of this method need to be here?
|
||||
bool TopoShape::findPlane(gp_Pln &pln, double tol) const {
|
||||
#ifdef FC_USE_TNP_FIX
|
||||
bool TopoShape::findPlane(gp_Pln& pln, double tol, double atol) const
|
||||
{
|
||||
if (_Shape.IsNull()) {
|
||||
return false;
|
||||
}
|
||||
if (tol < 0.0) {
|
||||
tol = Precision::Confusion();
|
||||
}
|
||||
if (atol < 0.0) {
|
||||
atol = Precision::Angular();
|
||||
}
|
||||
TopoDS_Shape shape;
|
||||
if (countSubShapes(TopAbs_EDGE) == 1) {
|
||||
// To deal with OCCT bug of wrong edge transformation
|
||||
shape = BRepBuilderAPI_Copy(_Shape).Shape();
|
||||
}
|
||||
else {
|
||||
shape = _Shape;
|
||||
}
|
||||
try {
|
||||
bool found = false;
|
||||
// BRepLib_FindSurface only really works on edges. We'll deal face first
|
||||
for (auto& shape : getSubShapes(TopAbs_FACE)) {
|
||||
gp_Pln plane;
|
||||
auto face = TopoDS::Face(shape);
|
||||
BRepAdaptor_Surface adapt(face);
|
||||
if (adapt.GetType() == GeomAbs_Plane) {
|
||||
plane = adapt.Plane();
|
||||
}
|
||||
else {
|
||||
TopLoc_Location loc;
|
||||
Handle(Geom_Surface) surf = BRep_Tool::Surface(face, loc);
|
||||
GeomLib_IsPlanarSurface check(surf);
|
||||
if (check.IsPlanar()) {
|
||||
plane = check.Plan();
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
found = true;
|
||||
pln = plane;
|
||||
}
|
||||
else if (!pln.Position().IsCoplanar(plane.Position(), tol, atol)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there is free edges (i.e. edges does not belong to any face)
|
||||
if (TopExp_Explorer(getShape(), TopAbs_EDGE, TopAbs_FACE).More()) {
|
||||
// Copy shape to work around OCC transformation bug, that is, if
|
||||
// edge has transformation, but underlying geometry does not (or the
|
||||
// other way round), BRepLib_FindSurface returns a plane with the
|
||||
// wrong transformation
|
||||
BRepLib_FindSurface finder(BRepBuilderAPI_Copy(shape).Shape(), tol, Standard_True);
|
||||
if (!finder.Found()) {
|
||||
return false;
|
||||
}
|
||||
pln = GeomAdaptor_Surface(finder.Surface()).Plane();
|
||||
found = true;
|
||||
}
|
||||
|
||||
// Check for free vertexes
|
||||
auto vertexes = getSubShapes(TopAbs_VERTEX, TopAbs_EDGE);
|
||||
if (vertexes.size()) {
|
||||
if (!found && vertexes.size() > 2) {
|
||||
BRep_Builder builder;
|
||||
TopoDS_Compound comp;
|
||||
builder.MakeCompound(comp);
|
||||
for (int i = 0, c = (int)vertexes.size() - 1; i < c; ++i) {
|
||||
builder.Add(comp,
|
||||
BRepBuilderAPI_MakeEdge(TopoDS::Vertex(vertexes[i]),
|
||||
TopoDS::Vertex(vertexes[i + 1]))
|
||||
.Edge());
|
||||
}
|
||||
BRepLib_FindSurface finder(comp, tol, Standard_True);
|
||||
if (!finder.Found()) {
|
||||
return false;
|
||||
}
|
||||
pln = GeomAdaptor_Surface(finder.Surface()).Plane();
|
||||
return true;
|
||||
}
|
||||
|
||||
double tt = tol * tol;
|
||||
for (auto& v : vertexes) {
|
||||
if (pln.SquareDistance(BRep_Tool::Pnt(TopoDS::Vertex(v))) > tt) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// To make the returned plane normal more stable, if the shape has any
|
||||
// face, use the normal of the first face.
|
||||
if (hasSubShape(TopAbs_FACE)) {
|
||||
shape = getSubShape(TopAbs_FACE, 1);
|
||||
BRepAdaptor_Surface adapt(TopoDS::Face(shape));
|
||||
double u =
|
||||
adapt.FirstUParameter() + (adapt.LastUParameter() - adapt.FirstUParameter()) / 2.;
|
||||
double v =
|
||||
adapt.FirstVParameter() + (adapt.LastVParameter() - adapt.FirstVParameter()) / 2.;
|
||||
BRepLProp_SLProps prop(adapt, u, v, 2, Precision::Confusion());
|
||||
if (prop.IsNormalDefined()) {
|
||||
gp_Pnt pnt;
|
||||
gp_Vec vec;
|
||||
// handles the orientation state of the shape
|
||||
BRepGProp_Face(TopoDS::Face(shape)).Normal(u, v, pnt, vec);
|
||||
pln = gp_Pln(pnt, gp_Dir(vec));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Standard_Failure& e) {
|
||||
// For some reason the above BRepBuilderAPI_Copy failed to copy
|
||||
// the geometry of some edge, causing exception with message
|
||||
// BRepAdaptor_Curve::No geometry. However, without the above
|
||||
// copy, circular edges often have the wrong transformation!
|
||||
FC_LOG("failed to find surface: " << e.GetMessageString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#else
|
||||
bool TopoShape::findPlane(gp_Pln &pln, double tol, double atol) const {
|
||||
if(_Shape.IsNull())
|
||||
return false;
|
||||
TopoDS_Shape shape = _Shape;
|
||||
@@ -4004,6 +4126,7 @@ bool TopoShape::findPlane(gp_Pln &pln, double tol) const {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool TopoShape::isInfinite() const
|
||||
{
|
||||
|
||||
@@ -477,7 +477,7 @@ public:
|
||||
bool analyze(bool runBopCheck, std::ostream&) const;
|
||||
bool isClosed() const;
|
||||
bool isCoplanar(const TopoShape& other, double tol = -1) const;
|
||||
bool findPlane(gp_Pln& plane, double tol = -1) const;
|
||||
bool findPlane(gp_Pln& plane, double tol = -1, double atol = -1) const;
|
||||
/// Returns true if the expansion of the shape is infinite, false otherwise
|
||||
bool isInfinite() const;
|
||||
/// Checks whether the shape is a planar face
|
||||
@@ -1153,16 +1153,14 @@ public:
|
||||
* a self reference so that multiple operations can be carried out
|
||||
* for the same shape in the same line of code.
|
||||
*/
|
||||
// TODO: This code was transferred in Feb 2024 as part of the toponaming project, but appears to be
|
||||
// unused. It is potentially useful if debugged.
|
||||
// TopoShape &makeElementPrismUntil(const TopoShape &base,
|
||||
// const TopoShape& profile,
|
||||
// const TopoShape& supportFace,
|
||||
// const TopoShape& upToFace,
|
||||
// const gp_Dir& direction,
|
||||
// PrismMode mode,
|
||||
// Standard_Boolean checkLimits = Standard_True,
|
||||
// const char *op=nullptr);
|
||||
TopoShape& makeElementPrismUntil(const TopoShape& base,
|
||||
const TopoShape& profile,
|
||||
const TopoShape& supportFace,
|
||||
const TopoShape& upToFace,
|
||||
const gp_Dir& direction,
|
||||
PrismMode mode,
|
||||
Standard_Boolean checkLimits = Standard_True,
|
||||
const char* op = nullptr);
|
||||
|
||||
/** Make a prism based on this shape that is either depression or protrusion of a profile shape up to a given face
|
||||
*
|
||||
@@ -1181,25 +1179,23 @@ public:
|
||||
*
|
||||
* @return Return the generated new shape. The TopoShape itself is not modified.
|
||||
*/
|
||||
// TODO: This code was transferred in Feb 2024 as part of the toponaming project, but appears to be
|
||||
// unused. It is potentially useful if debugged.
|
||||
// TopoShape makeElementPrismUntil(const TopoShape& profile,
|
||||
// const TopoShape& supportFace,
|
||||
// const TopoShape& upToFace,
|
||||
// const gp_Dir& direction,
|
||||
// PrismMode mode,
|
||||
// Standard_Boolean checkLimits = Standard_True,
|
||||
// const char *op=nullptr) const
|
||||
// {
|
||||
// return TopoShape(0,Hasher).makeElementPrismUntil(*this,
|
||||
// profile,
|
||||
// supportFace,
|
||||
// upToFace,
|
||||
// direction,
|
||||
// mode,
|
||||
// checkLimits,
|
||||
// op);
|
||||
// }
|
||||
TopoShape makeElementPrismUntil(const TopoShape& profile,
|
||||
const TopoShape& supportFace,
|
||||
const TopoShape& upToFace,
|
||||
const gp_Dir& direction,
|
||||
PrismMode mode,
|
||||
Standard_Boolean checkLimits = Standard_True,
|
||||
const char* op = nullptr) const
|
||||
{
|
||||
return TopoShape(0, Hasher).makeElementPrismUntil(*this,
|
||||
profile,
|
||||
supportFace,
|
||||
upToFace,
|
||||
direction,
|
||||
mode,
|
||||
checkLimits,
|
||||
op);
|
||||
}
|
||||
|
||||
|
||||
/* Make a shell or solid by sweeping profile wire along a spine
|
||||
|
||||
@@ -158,6 +158,9 @@ Data::ElementMapPtr TopoShape::resetElementMap(Data::ElementMapPtr elementMap)
|
||||
_cache->subLocation.Identity();
|
||||
_subLocation.Identity();
|
||||
_parentCache.reset();
|
||||
if ( ! elementMap->hasher && Hasher ) {
|
||||
elementMap->hasher = Hasher;
|
||||
}
|
||||
}
|
||||
return Data::ComplexGeoData::resetElementMap(elementMap);
|
||||
}
|
||||
@@ -1432,7 +1435,6 @@ TopoShape& TopoShape::makeShapeWithElementMap(const TopoDS_Shape& shape,
|
||||
if (otherMap.count() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 1; i <= otherMap.count(); i++) {
|
||||
const auto& otherElement = otherMap.find(incomingShape._Shape, i);
|
||||
// Find all new objects that are a modification of the old object
|
||||
@@ -1751,7 +1753,6 @@ TopoShape& TopoShape::makeShapeWithElementMap(const TopoDS_Shape& shape,
|
||||
elementMap()
|
||||
->encodeElementName(element[0], first_name, ss, &sids, Tag, op, first_key.tag);
|
||||
elementMap()->setElementName(element, first_name, Tag, &sids);
|
||||
|
||||
if (!delayed && first_key.shapetype < 3) {
|
||||
newNames.erase(itName);
|
||||
}
|
||||
@@ -1849,7 +1850,7 @@ TopoShape& TopoShape::makeShapeWithElementMap(const TopoDS_Shape& shape,
|
||||
|
||||
elementMap()->encodeElementName(indexedName[0], newName, ss, &sids, Tag, op);
|
||||
elementMap()->setElementName(indexedName, newName, Tag, &sids);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4231,192 +4232,191 @@ TopoShape& TopoShape::makeElementPrism(const TopoShape& base, const gp_Vec& vec,
|
||||
return makeElementShape(mkPrism, base, op);
|
||||
}
|
||||
|
||||
// TODO: This code was transferred in Feb 2024 as part of the toponaming project, but appears to be
|
||||
// unused. It is potentially useful if debugged.
|
||||
// TopoShape& TopoShape::makeElementPrismUntil(const TopoShape& _base,
|
||||
// const TopoShape& profile,
|
||||
// const TopoShape& supportFace,
|
||||
// const TopoShape& __uptoface,
|
||||
// const gp_Dir& direction,
|
||||
// PrismMode Mode,
|
||||
// Standard_Boolean checkLimits,
|
||||
// const char* op)
|
||||
//{
|
||||
// if (!op) {
|
||||
// op = Part::OpCodes::Prism;
|
||||
// }
|
||||
//
|
||||
// BRepFeat_MakePrism PrismMaker;
|
||||
//
|
||||
// TopoShape _uptoface(__uptoface);
|
||||
// if (checkLimits && _uptoface.shapeType(true) == TopAbs_FACE
|
||||
// && !BRep_Tool::NaturalRestriction(TopoDS::Face(_uptoface.getShape()))) {
|
||||
// // When using the face with BRepFeat_MakePrism::Perform(const TopoDS_Shape& Until)
|
||||
// // then the algorithm expects that the 'NaturalRestriction' flag is set in order
|
||||
// // to work as expected.
|
||||
// BRep_Builder builder;
|
||||
// _uptoface = _uptoface.makeElementCopy();
|
||||
// builder.NaturalRestriction(TopoDS::Face(_uptoface.getShape()), Standard_True);
|
||||
// }
|
||||
//
|
||||
// TopoShape uptoface(_uptoface);
|
||||
// TopoShape base(_base);
|
||||
//
|
||||
// if (base.isNull()) {
|
||||
// Mode = PrismMode::None;
|
||||
// base = profile;
|
||||
// }
|
||||
//
|
||||
// // Check whether the face has limits or not. Unlimited faces have no wire
|
||||
// // Note: Datum planes are always unlimited
|
||||
// if (checkLimits && uptoface.hasSubShape(TopAbs_WIRE)) {
|
||||
// TopoDS_Face face = TopoDS::Face(uptoface.getShape());
|
||||
// bool remove_limits = false;
|
||||
// // Remove the limits of the upToFace so that the extrusion works even if profile is larger
|
||||
// // than the upToFace
|
||||
// for (auto& sketchface : profile.getSubTopoShapes(TopAbs_FACE)) {
|
||||
// // Get outermost wire of sketch face
|
||||
// TopoShape outerWire = sketchface.splitWires();
|
||||
// BRepProj_Projection proj(TopoDS::Wire(outerWire.getShape()), face, direction);
|
||||
// if (!proj.More() || !proj.Current().Closed()) {
|
||||
// remove_limits = true;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // It must also be checked that all projected inner wires of the upToFace
|
||||
// // lie outside the sketch shape. If this is not the case then the sketch
|
||||
// // shape is not completely covered by the upToFace. See #0003141
|
||||
// if (!remove_limits) {
|
||||
// std::vector<TopoShape> wires;
|
||||
// uptoface.splitWires(&wires);
|
||||
// for (auto& w : wires) {
|
||||
// BRepProj_Projection proj(TopoDS::Wire(w.getShape()),
|
||||
// profile.getShape(),
|
||||
// -direction);
|
||||
// if (proj.More()) {
|
||||
// remove_limits = true;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (remove_limits) {
|
||||
// // Note: Using an unlimited face every time gives unnecessary failures for concave
|
||||
// faces TopLoc_Location loc = face.Location(); BRepAdaptor_Surface adapt(face,
|
||||
// Standard_False);
|
||||
// // use the placement of the adapter, not of the upToFace
|
||||
// loc = TopLoc_Location(adapt.Trsf());
|
||||
// BRepBuilderAPI_MakeFace mkFace(adapt.Surface().Surface(), Precision::Confusion());
|
||||
// if (!mkFace.IsDone()) {
|
||||
// remove_limits = false;
|
||||
// }
|
||||
// else {
|
||||
// uptoface.setShape(located(mkFace.Shape(), loc), false);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// TopoShape uptofaceCopy = uptoface;
|
||||
// bool checkBase = false;
|
||||
// auto retry = [&]() {
|
||||
// if (!uptoface.isSame(_uptoface)) {
|
||||
// // retry using the original up to face in case unnecessary failure
|
||||
// // due to removing the limits
|
||||
// uptoface = _uptoface;
|
||||
// return true;
|
||||
// }
|
||||
// if ((!_base.isNull() && base.isSame(_base)) || (_base.isNull() && base.isSame(profile))) {
|
||||
// // It is unclear under exactly what condition extrude up to face
|
||||
// // can fail. Either the support face or the up to face must be part
|
||||
// // of the base, or maybe some thing else.
|
||||
// //
|
||||
// // To deal with it, we retry again by disregard the supplied base,
|
||||
// // and use up to face to extrude our own base. Later on, use the
|
||||
// // supplied base (i.e. _base) to calculate the final shape if the
|
||||
// // mode is FuseWithBase or CutWithBase.
|
||||
// checkBase = true;
|
||||
// uptoface = uptofaceCopy;
|
||||
// base.makeElementPrism(_uptoface, direction);
|
||||
// return true;
|
||||
// }
|
||||
// return false;
|
||||
// };
|
||||
//
|
||||
// std::vector<TopoShape> srcShapes;
|
||||
// TopoShape result;
|
||||
// for (;;) {
|
||||
// try {
|
||||
// result = base;
|
||||
//
|
||||
// // We do not rely on BRepFeat_MakePrism to perform fuse or cut for
|
||||
// // us because of its poor support of shape history.
|
||||
// auto mode = PrismMode::None;
|
||||
//
|
||||
// for (auto& face : profile.getSubTopoShapes(
|
||||
// profile.hasSubShape(TopAbs_FACE) ? TopAbs_FACE : TopAbs_WIRE)) {
|
||||
// srcShapes.clear();
|
||||
// if (!profile.isNull() && !result.findShape(profile.getShape())) {
|
||||
// srcShapes.push_back(profile);
|
||||
// }
|
||||
// if (!supportFace.isNull() && !result.findShape(supportFace.getShape())) {
|
||||
// srcShapes.push_back(supportFace);
|
||||
// }
|
||||
//
|
||||
// // DO NOT include uptoface for element mapping. Because OCCT
|
||||
// // BRepFeat_MakePrism will report all top extruded face being
|
||||
// // modified by the uptoface. If there are more than one face in
|
||||
// // the profile, this will cause unnecessary duplicated element
|
||||
// // mapped name. And will also disrupte element history tracing
|
||||
// // back to the profile sketch.
|
||||
// //
|
||||
// // if (!uptoface.isNull() && !this->findShape(uptoface.getShape()))
|
||||
// // srcShapes.push_back(uptoface);
|
||||
//
|
||||
// srcShapes.push_back(result);
|
||||
//
|
||||
// PrismMaker.Init(result.getShape(),
|
||||
// face.getShape(),
|
||||
// TopoDS::Face(supportFace.getShape()),
|
||||
// direction,
|
||||
// mode,
|
||||
// Standard_False);
|
||||
// mode = PrismMode::FuseWithBase;
|
||||
//
|
||||
// PrismMaker.Perform(uptoface.getShape());
|
||||
//
|
||||
// if (!PrismMaker.IsDone() || PrismMaker.Shape().IsNull()) {
|
||||
// FC_THROWM(Base::CADKernelError, "BRepFeat_MakePrism: extrusion failed");
|
||||
// }
|
||||
//
|
||||
// result.makeElementShape(PrismMaker, srcShapes, uptoface, op);
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
// catch (Base::Exception&) {
|
||||
// if (!retry()) {
|
||||
// throw;
|
||||
// }
|
||||
// }
|
||||
// catch (Standard_Failure&) {
|
||||
// if (!retry()) {
|
||||
// throw;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (!_base.isNull() && Mode != PrismMode::None) {
|
||||
// if (Mode == PrismMode::FuseWithBase) {
|
||||
// result.makeElementFuse({_base, result});
|
||||
// }
|
||||
// else {
|
||||
// result.makeElementCut({_base, result});
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// *this = result;
|
||||
// return *this;
|
||||
//}
|
||||
TopoShape& TopoShape::makeElementPrismUntil(const TopoShape& _base,
|
||||
const TopoShape& profile,
|
||||
const TopoShape& supportFace,
|
||||
const TopoShape& __uptoface,
|
||||
const gp_Dir& direction,
|
||||
PrismMode Mode,
|
||||
Standard_Boolean checkLimits,
|
||||
const char* op)
|
||||
{
|
||||
if (!op) {
|
||||
op = Part::OpCodes::Prism;
|
||||
}
|
||||
|
||||
BRepFeat_MakePrism PrismMaker;
|
||||
|
||||
TopoShape _uptoface(__uptoface);
|
||||
if (checkLimits && _uptoface.shapeType(true) == TopAbs_FACE
|
||||
&& !BRep_Tool::NaturalRestriction(TopoDS::Face(_uptoface.getShape()))) {
|
||||
// When using the face with BRepFeat_MakePrism::Perform(const TopoDS_Shape& Until)
|
||||
// then the algorithm expects that the 'NaturalRestriction' flag is set in order
|
||||
// to work as expected.
|
||||
BRep_Builder builder;
|
||||
_uptoface = _uptoface.makeElementCopy();
|
||||
builder.NaturalRestriction(TopoDS::Face(_uptoface.getShape()), Standard_True);
|
||||
}
|
||||
|
||||
TopoShape uptoface(_uptoface);
|
||||
TopoShape base(_base);
|
||||
|
||||
if (base.isNull()) {
|
||||
Mode = PrismMode::None;
|
||||
base = profile;
|
||||
}
|
||||
|
||||
// Check whether the face has limits or not. Unlimited faces have no wire
|
||||
// Note: Datum planes are always unlimited
|
||||
if (checkLimits && uptoface.hasSubShape(TopAbs_WIRE)) {
|
||||
TopoDS_Face face = TopoDS::Face(uptoface.getShape());
|
||||
bool remove_limits = false;
|
||||
// Remove the limits of the upToFace so that the extrusion works even if profile is larger
|
||||
// than the upToFace
|
||||
for (auto& sketchface : profile.getSubTopoShapes(TopAbs_FACE)) {
|
||||
// Get outermost wire of sketch face
|
||||
TopoShape outerWire = sketchface.splitWires();
|
||||
BRepProj_Projection proj(TopoDS::Wire(outerWire.getShape()), face, direction);
|
||||
if (!proj.More() || !proj.Current().Closed()) {
|
||||
remove_limits = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// It must also be checked that all projected inner wires of the upToFace
|
||||
// lie outside the sketch shape. If this is not the case then the sketch
|
||||
// shape is not completely covered by the upToFace. See #0003141
|
||||
if (!remove_limits) {
|
||||
std::vector<TopoShape> wires;
|
||||
uptoface.splitWires(&wires);
|
||||
for (auto& w : wires) {
|
||||
BRepProj_Projection proj(TopoDS::Wire(w.getShape()),
|
||||
profile.getShape(),
|
||||
-direction);
|
||||
if (proj.More()) {
|
||||
remove_limits = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (remove_limits) {
|
||||
// Note: Using an unlimited face every time gives unnecessary failures for concave
|
||||
// faces
|
||||
TopLoc_Location loc = face.Location();
|
||||
BRepAdaptor_Surface adapt(face, Standard_False);
|
||||
// use the placement of the adapter, not of the upToFace
|
||||
loc = TopLoc_Location(adapt.Trsf());
|
||||
BRepBuilderAPI_MakeFace mkFace(adapt.Surface().Surface(), Precision::Confusion());
|
||||
if (!mkFace.IsDone()) {
|
||||
remove_limits = false;
|
||||
}
|
||||
else {
|
||||
uptoface.setShape(located(mkFace.Shape(), loc), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TopoShape uptofaceCopy = uptoface;
|
||||
bool checkBase = false;
|
||||
auto retry = [&]() {
|
||||
if (!uptoface.isSame(_uptoface)) {
|
||||
// retry using the original up to face in case unnecessary failure
|
||||
// due to removing the limits
|
||||
uptoface = _uptoface;
|
||||
return true;
|
||||
}
|
||||
if ((!_base.isNull() && base.isSame(_base)) || (_base.isNull() && base.isSame(profile))) {
|
||||
// It is unclear under exactly what condition extrude up to face
|
||||
// can fail. Either the support face or the up to face must be part
|
||||
// of the base, or maybe some thing else.
|
||||
//
|
||||
// To deal with it, we retry again by disregard the supplied base,
|
||||
// and use up to face to extrude our own base. Later on, use the
|
||||
// supplied base (i.e. _base) to calculate the final shape if the
|
||||
// mode is FuseWithBase or CutWithBase.
|
||||
checkBase = true;
|
||||
uptoface = uptofaceCopy;
|
||||
base.makeElementPrism(_uptoface, direction);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
std::vector<TopoShape> srcShapes;
|
||||
TopoShape result;
|
||||
for (;;) {
|
||||
try {
|
||||
result = base;
|
||||
|
||||
// We do not rely on BRepFeat_MakePrism to perform fuse or cut for
|
||||
// us because of its poor support of shape history.
|
||||
auto mode = PrismMode::None;
|
||||
|
||||
for (auto& face : profile.getSubTopoShapes(
|
||||
profile.hasSubShape(TopAbs_FACE) ? TopAbs_FACE : TopAbs_WIRE)) {
|
||||
srcShapes.clear();
|
||||
if (!profile.isNull() && !result.findShape(profile.getShape())) {
|
||||
srcShapes.push_back(profile);
|
||||
}
|
||||
if (!supportFace.isNull() && !result.findShape(supportFace.getShape())) {
|
||||
srcShapes.push_back(supportFace);
|
||||
}
|
||||
|
||||
// DO NOT include uptoface for element mapping. Because OCCT
|
||||
// BRepFeat_MakePrism will report all top extruded face being
|
||||
// modified by the uptoface. If there are more than one face in
|
||||
// the profile, this will cause unnecessary duplicated element
|
||||
// mapped name. And will also disrupte element history tracing
|
||||
// back to the profile sketch.
|
||||
//
|
||||
// if (!uptoface.isNull() && !this->findShape(uptoface.getShape()))
|
||||
// srcShapes.push_back(uptoface);
|
||||
|
||||
srcShapes.push_back(result);
|
||||
|
||||
PrismMaker.Init(result.getShape(),
|
||||
face.getShape(),
|
||||
TopoDS::Face(supportFace.getShape()),
|
||||
direction,
|
||||
mode,
|
||||
Standard_False);
|
||||
mode = PrismMode::FuseWithBase;
|
||||
|
||||
PrismMaker.Perform(uptoface.getShape());
|
||||
|
||||
if (!PrismMaker.IsDone() || PrismMaker.Shape().IsNull()) {
|
||||
FC_THROWM(Base::CADKernelError, "BRepFeat_MakePrism: extrusion failed");
|
||||
}
|
||||
|
||||
result.makeElementShape(PrismMaker, srcShapes, uptoface, op);
|
||||
}
|
||||
break;
|
||||
}
|
||||
catch (Base::Exception&) {
|
||||
if (!retry()) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
catch (Standard_Failure&) {
|
||||
if (!retry()) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!_base.isNull() && Mode != PrismMode::None) {
|
||||
if (Mode == PrismMode::FuseWithBase) {
|
||||
result.makeElementFuse({_base, result});
|
||||
}
|
||||
else {
|
||||
result.makeElementCut({_base, result});
|
||||
}
|
||||
}
|
||||
|
||||
*this = result;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TopoShape& TopoShape::makeElementRevolve(const TopoShape& _base,
|
||||
const gp_Ax1& axis,
|
||||
|
||||
@@ -122,7 +122,11 @@ static Py_hash_t _TopoShapeHash(PyObject* self)
|
||||
"This reference is no longer valid!");
|
||||
return 0;
|
||||
}
|
||||
#if OCC_VERSION_HEX >= 0x070800
|
||||
return std::hash<TopoDS_Shape> {}(static_cast<TopoShapePy*>(self)->getTopoShapePtr()->getShape());
|
||||
#else
|
||||
return static_cast<TopoShapePy*>(self)->getTopoShapePtr()->getShape().HashCode(INT_MAX);
|
||||
#endif
|
||||
}
|
||||
|
||||
struct TopoShapePyInit
|
||||
|
||||
@@ -87,6 +87,7 @@ short Feature::mustExecute() const
|
||||
return Part::Feature::mustExecute();
|
||||
}
|
||||
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
TopoDS_Shape Feature::getSolid(const TopoDS_Shape& shape)
|
||||
{
|
||||
if (shape.IsNull())
|
||||
@@ -100,6 +101,16 @@ TopoDS_Shape Feature::getSolid(const TopoDS_Shape& shape)
|
||||
return {};
|
||||
}
|
||||
|
||||
TopoShape Feature::getSolid(const TopoShape& shape)
|
||||
{
|
||||
if (shape.isNull()) {
|
||||
throw Part::NullShapeException("Null shape");
|
||||
}
|
||||
auto res = shape.getSubTopoShape(TopAbs_SOLID, 1);
|
||||
res.fixSolidOrientation();
|
||||
return res;
|
||||
}
|
||||
|
||||
int Feature::countSolids(const TopoDS_Shape& shape, TopAbs_ShapeEnum type)
|
||||
{
|
||||
int result = 0;
|
||||
@@ -176,29 +187,40 @@ const TopoDS_Shape& Feature::getBaseShape() const {
|
||||
return result;
|
||||
}
|
||||
|
||||
Part::TopoShape Feature::getBaseTopoShape(bool silent) const {
|
||||
Part::TopoShape Feature::getBaseTopoShape(bool silent) const
|
||||
{
|
||||
Part::TopoShape result;
|
||||
|
||||
const Part::Feature* BaseObject = getBaseObject(silent);
|
||||
if (!BaseObject)
|
||||
if (!BaseObject) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if(BaseObject != BaseFeature.getValue()) {
|
||||
if (BaseObject->isDerivedFrom(PartDesign::ShapeBinder::getClassTypeId()) ||
|
||||
BaseObject->isDerivedFrom(PartDesign::SubShapeBinder::getClassTypeId()))
|
||||
{
|
||||
if(silent)
|
||||
if (BaseObject != BaseFeature.getValue()) {
|
||||
auto body = getFeatureBody();
|
||||
if (!body) {
|
||||
if (silent) {
|
||||
return result;
|
||||
}
|
||||
throw Base::RuntimeError("Missing container body");
|
||||
}
|
||||
if (BaseObject->isDerivedFrom(PartDesign::ShapeBinder::getClassTypeId())
|
||||
|| BaseObject->isDerivedFrom(PartDesign::SubShapeBinder::getClassTypeId())) {
|
||||
if (silent) {
|
||||
return result;
|
||||
}
|
||||
throw Base::ValueError("Base shape of shape binder cannot be used");
|
||||
}
|
||||
}
|
||||
|
||||
result = BaseObject->Shape.getShape();
|
||||
if(!silent) {
|
||||
if (result.isNull())
|
||||
if (!silent) {
|
||||
if (result.isNull()) {
|
||||
throw Base::ValueError("Base feature's TopoShape is invalid");
|
||||
if (!result.hasSubShape(TopAbs_SOLID))
|
||||
}
|
||||
if (!result.hasSubShape(TopAbs_SOLID)) {
|
||||
throw Base::ValueError("Base feature's shape is not a solid");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -231,6 +253,7 @@ gp_Pln Feature::makePlnFromPlane(const App::DocumentObject* obj)
|
||||
return gp_Pln(gp_Pnt(pos.x,pos.y,pos.z), gp_Dir(normal.x,normal.y,normal.z));
|
||||
}
|
||||
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
TopoDS_Shape Feature::makeShapeFromPlane(const App::DocumentObject* obj)
|
||||
{
|
||||
BRepBuilderAPI_MakeFace builder(makePlnFromPlane(obj));
|
||||
@@ -240,6 +263,16 @@ TopoDS_Shape Feature::makeShapeFromPlane(const App::DocumentObject* obj)
|
||||
return builder.Shape();
|
||||
}
|
||||
|
||||
TopoShape Feature::makeTopoShapeFromPlane(const App::DocumentObject* obj)
|
||||
{
|
||||
BRepBuilderAPI_MakeFace builder(makePlnFromPlane(obj));
|
||||
if (!builder.IsDone()) {
|
||||
throw Base::CADKernelError("Feature: Could not create shape from base plane");
|
||||
}
|
||||
|
||||
return TopoShape(obj->getID(), nullptr, builder.Shape());
|
||||
}
|
||||
|
||||
Body* Feature::getFeatureBody() const {
|
||||
|
||||
auto body = Base::freecad_dynamic_cast<Body>(_Body.getValue());
|
||||
|
||||
@@ -94,14 +94,18 @@ protected:
|
||||
/**
|
||||
* Get a solid of the given shape. If no solid is found an exception is raised.
|
||||
*/
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
static TopoDS_Shape getSolid(const TopoDS_Shape&);
|
||||
static int countSolids(const TopoDS_Shape&, TopAbs_ShapeEnum type = TopAbs_SOLID );
|
||||
TopoShape getSolid(const TopoShape&);
|
||||
static int countSolids(const TopoDS_Shape&, TopAbs_ShapeEnum type = TopAbs_SOLID);
|
||||
|
||||
/// Grab any point from the given face
|
||||
static const gp_Pnt getPointFromFace(const TopoDS_Face& f);
|
||||
/// Make a shape from a base plane (convenience method)
|
||||
static gp_Pln makePlnFromPlane(const App::DocumentObject* obj);
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
static TopoDS_Shape makeShapeFromPlane(const App::DocumentObject* obj);
|
||||
static TopoShape makeTopoShapeFromPlane(const App::DocumentObject* obj);
|
||||
};
|
||||
|
||||
using FeaturePython = App::FeaturePythonT<Feature>;
|
||||
|
||||
@@ -64,6 +64,7 @@ short FeatureAddSub::mustExecute() const
|
||||
return PartDesign::Feature::mustExecute();
|
||||
}
|
||||
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
TopoDS_Shape FeatureAddSub::refineShapeIfActive(const TopoDS_Shape& oldShape) const
|
||||
{
|
||||
if (this->Refine.getValue()) {
|
||||
@@ -83,6 +84,16 @@ TopoDS_Shape FeatureAddSub::refineShapeIfActive(const TopoDS_Shape& oldShape) co
|
||||
return oldShape;
|
||||
}
|
||||
|
||||
TopoShape FeatureAddSub::refineShapeIfActive(const TopoShape& oldShape) const
|
||||
{
|
||||
if (this->Refine.getValue()) {
|
||||
TopoShape shape(oldShape);
|
||||
// this->fixShape(shape); // Todo: Not clear that this is required
|
||||
return shape.makeElementRefine();
|
||||
}
|
||||
return oldShape;
|
||||
}
|
||||
|
||||
void FeatureAddSub::getAddSubShape(Part::TopoShape &addShape, Part::TopoShape &subShape)
|
||||
{
|
||||
if (addSubType == Additive)
|
||||
|
||||
@@ -54,7 +54,9 @@ public:
|
||||
protected:
|
||||
Type addSubType{Additive};
|
||||
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
TopoDS_Shape refineShapeIfActive(const TopoDS_Shape&) const;
|
||||
TopoShape refineShapeIfActive(const TopoShape&) const;
|
||||
};
|
||||
|
||||
using FeatureAddSubPython = App::FeaturePythonT<FeatureAddSub>;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2020 Werner Mayer <wmayer[at]users.sourceforge.net> *
|
||||
* *
|
||||
@@ -38,9 +39,12 @@
|
||||
#include <App/Document.h>
|
||||
#include <Base/Tools.h>
|
||||
#include <Mod/Part/App/ExtrusionHelper.h>
|
||||
#include "Mod/Part/App/TopoShapeOpCode.h"
|
||||
|
||||
#include "FeatureExtrude.h"
|
||||
|
||||
FC_LOG_LEVEL_INIT("PartDesign", true, true)
|
||||
|
||||
using namespace PartDesign;
|
||||
|
||||
PROPERTY_SOURCE(PartDesign::FeatureExtrude, PartDesign::ProfileBased)
|
||||
@@ -126,6 +130,7 @@ bool FeatureExtrude::hasTaperedAngle() const
|
||||
fabs(TaperAngle2.getValue()) > Base::toRadians(Precision::Angular());
|
||||
}
|
||||
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
void FeatureExtrude::generatePrism(TopoDS_Shape& prism,
|
||||
const TopoDS_Shape& sketchshape,
|
||||
const std::string& method,
|
||||
@@ -246,6 +251,72 @@ void FeatureExtrude::generatePrism(TopoDS_Shape& prism,
|
||||
}
|
||||
}
|
||||
|
||||
void FeatureExtrude::generatePrism(TopoShape& prism,
|
||||
TopoShape sketchTopoShape,
|
||||
const std::string& method,
|
||||
const gp_Dir& dir,
|
||||
const double L,
|
||||
const double L2,
|
||||
const bool midplane,
|
||||
const bool reversed)
|
||||
{
|
||||
auto sketchShape = sketchTopoShape.getShape();
|
||||
if (method == "Length" || method == "TwoLengths" || method == "ThroughAll") {
|
||||
double Ltotal = L;
|
||||
double Loffset = 0.;
|
||||
if (method == "ThroughAll") {
|
||||
Ltotal = getThroughAllLength();
|
||||
}
|
||||
|
||||
|
||||
if (method == "TwoLengths") {
|
||||
// midplane makes no sense here
|
||||
Ltotal += L2;
|
||||
if (reversed) {
|
||||
Loffset = -L;
|
||||
}
|
||||
else if (midplane) {
|
||||
Loffset = -0.5 * (L2 + L);
|
||||
}
|
||||
else {
|
||||
Loffset = -L2;
|
||||
}
|
||||
}
|
||||
else if (midplane) {
|
||||
Loffset = -Ltotal / 2;
|
||||
}
|
||||
|
||||
if (method == "TwoLengths" || midplane) {
|
||||
gp_Trsf mov;
|
||||
mov.SetTranslation(Loffset * gp_Vec(dir));
|
||||
TopLoc_Location loc(mov);
|
||||
sketchTopoShape.move(loc);
|
||||
}
|
||||
else if (reversed) {
|
||||
Ltotal *= -1.0;
|
||||
}
|
||||
|
||||
// Without taper angle we create a prism because its shells are in every case no B-splines
|
||||
// and can therefore be use as support for further features like Pads, Lofts etc. B-spline
|
||||
// shells can break certain features, see e.g.
|
||||
// https://forum.freecad.org/viewtopic.php?p=560785#p560785 It is better not to use
|
||||
// BRepFeat_MakePrism here even if we have a support because the resulting shape creates
|
||||
// problems with Pocket
|
||||
try {
|
||||
prism.makeElementPrism(sketchTopoShape, Ltotal * gp_Vec(dir)); // finite prism
|
||||
}
|
||||
catch (Standard_Failure&) {
|
||||
throw Base::RuntimeError("FeatureExtrusion: Length: Could not extrude the sketch!");
|
||||
}
|
||||
}
|
||||
else {
|
||||
std::stringstream str;
|
||||
str << "FeatureExtrusion: Internal error: Unknown method '" << method
|
||||
<< "' for generatePrism()";
|
||||
throw Base::RuntimeError(str.str());
|
||||
}
|
||||
}
|
||||
|
||||
void FeatureExtrude::generateTaperedPrism(TopoDS_Shape& prism,
|
||||
const TopoDS_Shape& sketchshape,
|
||||
const std::string& method,
|
||||
@@ -349,3 +420,335 @@ void FeatureExtrude::updateProperties(const std::string &method)
|
||||
Reversed.setReadOnly(!isReversedEnabled);
|
||||
UpToFace.setReadOnly(!isUpToFaceEnabled);
|
||||
}
|
||||
|
||||
void FeatureExtrude::setupObject()
|
||||
{
|
||||
ProfileBased::setupObject();
|
||||
}
|
||||
|
||||
App::DocumentObjectExecReturn* FeatureExtrude::buildExtrusion(ExtrudeOptions options)
|
||||
{
|
||||
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());
|
||||
|
||||
// Validate parameters
|
||||
double L = Length.getValue();
|
||||
if ((method == "Length") && (L < Precision::Confusion())) {
|
||||
return new App::DocumentObjectExecReturn(
|
||||
QT_TRANSLATE_NOOP("Exception", "Length too small"));
|
||||
}
|
||||
double L2 = 0;
|
||||
if ((method == "TwoLengths")) {
|
||||
L2 = Length2.getValue();
|
||||
if (std::abs(L2) < Precision::Confusion()) {
|
||||
return new App::DocumentObjectExecReturn(
|
||||
QT_TRANSLATE_NOOP("Exception", "Second length too small"));
|
||||
}
|
||||
}
|
||||
|
||||
Part::Feature* obj = nullptr;
|
||||
TopoShape sketchshape;
|
||||
try {
|
||||
obj = getVerifiedObject();
|
||||
if (makeface) {
|
||||
sketchshape = getTopoShapeVerifiedFace();
|
||||
}
|
||||
else {
|
||||
std::vector<TopoShape> shapes;
|
||||
bool hasEdges = false;
|
||||
auto subs = Profile.getSubValues(false);
|
||||
if (subs.empty()) {
|
||||
subs.emplace_back("");
|
||||
}
|
||||
bool failed = false;
|
||||
for (auto& sub : subs) {
|
||||
if (sub.empty() && subs.size() > 1) {
|
||||
continue;
|
||||
}
|
||||
TopoShape shape = Part::Feature::getTopoShape(obj, sub.c_str(), true);
|
||||
if (shape.isNull()) {
|
||||
FC_ERR(getFullName()
|
||||
<< ": failed to get profile shape " << obj->getFullName() << "." << sub);
|
||||
failed = true;
|
||||
}
|
||||
hasEdges = hasEdges || shape.hasSubShape(TopAbs_EDGE);
|
||||
shapes.push_back(shape);
|
||||
}
|
||||
if (failed) {
|
||||
return new App::DocumentObjectExecReturn(
|
||||
QT_TRANSLATE_NOOP("Exception", "Failed to obtain profile shape"));
|
||||
}
|
||||
if (hasEdges) {
|
||||
sketchshape.makeElementWires(shapes);
|
||||
}
|
||||
else {
|
||||
sketchshape.makeElementCompound(
|
||||
shapes,
|
||||
nullptr,
|
||||
TopoShape::SingleShapeCompoundCreationPolicy::returnShape);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const Base::Exception& e) {
|
||||
return new App::DocumentObjectExecReturn(e.what());
|
||||
}
|
||||
catch (const Standard_Failure& e) {
|
||||
return new App::DocumentObjectExecReturn(e.GetMessageString());
|
||||
}
|
||||
|
||||
// if the Base property has a valid shape, fuse the prism into it
|
||||
TopoShape base = getBaseTopoShape(true);
|
||||
|
||||
// get the normal vector of the sketch
|
||||
Base::Vector3d SketchVector = getProfileNormal();
|
||||
|
||||
try {
|
||||
this->positionByPrevious();
|
||||
auto invObjLoc = getLocation().Inverted();
|
||||
|
||||
auto invTrsf = invObjLoc.Transformation();
|
||||
|
||||
base.move(invObjLoc);
|
||||
|
||||
Base::Vector3d paddingDirection = computeDirection(SketchVector);
|
||||
|
||||
// create vector in padding direction with length 1
|
||||
gp_Dir dir(paddingDirection.x, paddingDirection.y, paddingDirection.z);
|
||||
|
||||
// The length of a gp_Dir is 1 so the resulting pad would have
|
||||
// the length L in the direction of dir. But we want to have its height in the
|
||||
// direction of the normal vector.
|
||||
// Therefore we must multiply L by the factor that is necessary
|
||||
// to make dir as long that its projection to the SketchVector
|
||||
// equals the SketchVector.
|
||||
// This is the scalar product of both vectors.
|
||||
// Since the pad length cannot be negative, the factor must not be negative.
|
||||
|
||||
double factor = fabs(dir * gp_Dir(SketchVector.x, SketchVector.y, SketchVector.z));
|
||||
|
||||
// factor would be zero if vectors are orthogonal
|
||||
if (factor < Precision::Confusion()) {
|
||||
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP(
|
||||
"Exception",
|
||||
"Creation failed because direction is orthogonal to sketch's normal vector"));
|
||||
}
|
||||
|
||||
// perform the length correction if not along custom vector
|
||||
if (AlongSketchNormal.getValue()) {
|
||||
L = L / factor;
|
||||
L2 = L2 / factor;
|
||||
}
|
||||
|
||||
// explicitly set the Direction so that the dialog shows also the used direction
|
||||
// if the sketch's normal vector was used
|
||||
Direction.setValue(paddingDirection);
|
||||
|
||||
dir.Transform(invTrsf);
|
||||
|
||||
if (sketchshape.isNull()) {
|
||||
return new App::DocumentObjectExecReturn(
|
||||
QT_TRANSLATE_NOOP("Exception", "Creating a face from sketch failed"));
|
||||
}
|
||||
sketchshape.move(invObjLoc);
|
||||
|
||||
TopoShape prism(0, getDocument()->getStringHasher());
|
||||
|
||||
if (method == "UpToFirst" || method == "UpToLast" || method == "UpToFace") {
|
||||
// Note: This will return an unlimited planar face if support is a datum plane
|
||||
TopoShape supportface = getTopoShapeSupportFace();
|
||||
supportface.move(invObjLoc);
|
||||
|
||||
if (Reversed.getValue()) {
|
||||
dir.Reverse();
|
||||
}
|
||||
|
||||
// Find a valid face or datum plane to extrude up to
|
||||
TopoShape upToFace;
|
||||
|
||||
if (method != "UpToShape") {
|
||||
if (method == "UpToFace") {
|
||||
getUpToFaceFromLinkSub(upToFace, UpToFace);
|
||||
upToFace.move(invObjLoc);
|
||||
}
|
||||
getUpToFace(upToFace, base, supportface, sketchshape, method, dir);
|
||||
addOffsetToFace(upToFace, dir, Offset.getValue());
|
||||
}
|
||||
|
||||
|
||||
if (!supportface.hasSubShape(TopAbs_WIRE)) {
|
||||
supportface = TopoShape();
|
||||
}
|
||||
if (legacyPocket) {
|
||||
auto mode =
|
||||
base.isNull() ? TopoShape::PrismMode::None : TopoShape::PrismMode::CutFromBase;
|
||||
prism = base.makeElementPrismUntil(sketchshape,
|
||||
supportface,
|
||||
upToFace,
|
||||
dir,
|
||||
mode,
|
||||
false /*CheckUpToFaceLimits.getValue()*/);
|
||||
// DO NOT assign id to the generated prism, because this prism is
|
||||
// actually the final result. We obtain the subtracted shape by cut
|
||||
// this prism with the original base. Assigning a minus self id here
|
||||
// will mess up with preselection highlight. It is enough to re-tag
|
||||
// the profile shape above.
|
||||
//
|
||||
// prism.Tag = -this->getID();
|
||||
|
||||
// And the really expensive way to get the SubShape...
|
||||
try {
|
||||
TopoShape result(0);
|
||||
if (base.isNull()) {
|
||||
result = prism;
|
||||
}
|
||||
else {
|
||||
result.makeElementCut({base, prism});
|
||||
}
|
||||
result = refineShapeIfActive(result);
|
||||
this->AddSubShape.setValue(result);
|
||||
}
|
||||
catch (Standard_Failure&) {
|
||||
return new App::DocumentObjectExecReturn(
|
||||
QT_TRANSLATE_NOOP("Exception", "Up to face: Could not get SubShape!"));
|
||||
}
|
||||
|
||||
if (getAddSubType() == Additive) {
|
||||
prism = base.makeElementFuse(this->AddSubShape.getShape());
|
||||
}
|
||||
else {
|
||||
prism = refineShapeIfActive(prism);
|
||||
}
|
||||
|
||||
this->Shape.setValue(getSolid(prism));
|
||||
return App::DocumentObject::StdReturn;
|
||||
}
|
||||
prism.makeElementPrismUntil(base,
|
||||
sketchshape,
|
||||
supportface,
|
||||
upToFace,
|
||||
dir,
|
||||
TopoShape::PrismMode::None,
|
||||
true /*CheckUpToFaceLimits.getValue()*/);
|
||||
}
|
||||
else {
|
||||
Part::ExtrusionParameters params;
|
||||
params.dir = dir;
|
||||
params.solid = makeface;
|
||||
params.taperAngleFwd = this->TaperAngle.getValue() * M_PI / 180.0;
|
||||
params.taperAngleRev = this->TaperAngle2.getValue() * M_PI / 180.0;
|
||||
if (L2 == 0.0 && Midplane.getValue()) {
|
||||
params.lengthFwd = L / 2;
|
||||
params.lengthRev = L / 2;
|
||||
if (params.taperAngleRev == 0.0) {
|
||||
params.taperAngleRev = params.taperAngleFwd;
|
||||
}
|
||||
}
|
||||
else {
|
||||
params.lengthFwd = L;
|
||||
params.lengthRev = L2;
|
||||
}
|
||||
if (std::fabs(params.taperAngleFwd) >= Precision::Angular()
|
||||
|| std::fabs(params.taperAngleRev) >= Precision::Angular()) {
|
||||
if (fabs(params.taperAngleFwd) > M_PI * 0.5 - Precision::Angular()
|
||||
|| fabs(params.taperAngleRev) > M_PI * 0.5 - Precision::Angular()) {
|
||||
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP(
|
||||
"Exception",
|
||||
"Magnitude of taper angle matches or exceeds 90 degrees"));
|
||||
}
|
||||
if (Reversed.getValue()) {
|
||||
params.dir.Reverse();
|
||||
}
|
||||
std::vector<TopoShape> drafts;
|
||||
Part::ExtrusionHelper::makeElementDraft(params, sketchshape, drafts, getDocument()->getStringHasher());
|
||||
if (drafts.empty()) {
|
||||
return new App::DocumentObjectExecReturn(
|
||||
QT_TRANSLATE_NOOP("Exception", "Padding with draft angle failed"));
|
||||
}
|
||||
prism.makeElementCompound(
|
||||
drafts,
|
||||
nullptr,
|
||||
TopoShape::SingleShapeCompoundCreationPolicy::returnShape);
|
||||
}
|
||||
else {
|
||||
generatePrism(prism,
|
||||
sketchshape,
|
||||
method,
|
||||
dir,
|
||||
L,
|
||||
L2,
|
||||
Midplane.getValue(),
|
||||
Reversed.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
// set the additive shape property for later usage in e.g. pattern
|
||||
prism = refineShapeIfActive(prism);
|
||||
this->AddSubShape.setValue(prism);
|
||||
|
||||
if (!base.isNull() && fuse) {
|
||||
prism.Tag = -this->getID();
|
||||
|
||||
// Let's call algorithm computing a fuse operation:
|
||||
TopoShape result(0, getDocument()->getStringHasher());
|
||||
try {
|
||||
const char* maker;
|
||||
switch (getAddSubType()) {
|
||||
case Subtractive:
|
||||
maker = Part::OpCodes::Cut;
|
||||
break;
|
||||
default:
|
||||
maker = Part::OpCodes::Fuse;
|
||||
}
|
||||
result.makeElementBoolean(maker, {base, prism});
|
||||
}
|
||||
catch (Standard_Failure&) {
|
||||
return new App::DocumentObjectExecReturn(
|
||||
QT_TRANSLATE_NOOP("Exception", "Fusion with base feature failed"));
|
||||
}
|
||||
// we have to get the solids (fuse sometimes creates compounds)
|
||||
auto solRes = this->getSolid(result);
|
||||
// lets check if the result is a solid
|
||||
if (solRes.isNull()) {
|
||||
return new App::DocumentObjectExecReturn(
|
||||
QT_TRANSLATE_NOOP("Exception", "Resulting shape is not a solid"));
|
||||
}
|
||||
|
||||
solRes = refineShapeIfActive(solRes);
|
||||
this->Shape.setValue(getSolid(solRes));
|
||||
}
|
||||
else if (prism.hasSubShape(TopAbs_SOLID)) {
|
||||
if (prism.countSubShapes(TopAbs_SOLID) > 1) {
|
||||
prism.makeElementFuse(prism.getSubTopoShapes(TopAbs_SOLID));
|
||||
}
|
||||
prism = refineShapeIfActive(prism);
|
||||
this->Shape.setValue(getSolid(prism));
|
||||
}
|
||||
else {
|
||||
prism = refineShapeIfActive(prism);
|
||||
this->Shape.setValue(prism);
|
||||
}
|
||||
|
||||
// eventually disable some settings that are not valid for the current method
|
||||
updateProperties(method);
|
||||
|
||||
return App::DocumentObject::StdReturn;
|
||||
}
|
||||
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 or multiple faces in a sketch are not allowed."));
|
||||
}
|
||||
else {
|
||||
return new App::DocumentObjectExecReturn(e.GetMessageString());
|
||||
}
|
||||
}
|
||||
catch (Base::Exception& e) {
|
||||
return new App::DocumentObjectExecReturn(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,12 +60,26 @@ public:
|
||||
/** @name methods override feature */
|
||||
//@{
|
||||
short mustExecute() const override;
|
||||
void setupObject() override;
|
||||
//@}
|
||||
|
||||
protected:
|
||||
Base::Vector3d computeDirection(const Base::Vector3d& sketchVector);
|
||||
bool hasTaperedAngle() const;
|
||||
|
||||
/// Options for buildExtrusion()
|
||||
enum class ExtrudeOption
|
||||
{
|
||||
MakeFace = 1,
|
||||
MakeFuse = 2,
|
||||
LegacyPocket = 4,
|
||||
InverseDirection = 8,
|
||||
};
|
||||
|
||||
using ExtrudeOptions = Base::Flags<ExtrudeOption>;
|
||||
|
||||
App::DocumentObjectExecReturn* buildExtrusion(ExtrudeOptions options);
|
||||
|
||||
/**
|
||||
* Generates an extrusion of the input sketchshape and stores it in the given \a prism
|
||||
*/
|
||||
@@ -78,6 +92,15 @@ protected:
|
||||
const bool midplane,
|
||||
const bool reversed);
|
||||
|
||||
void generatePrism(TopoShape& prism,
|
||||
TopoShape sketchshape,
|
||||
const std::string& method,
|
||||
const gp_Dir& direction,
|
||||
const double L,
|
||||
const double L2,
|
||||
const bool midplane,
|
||||
const bool reversed);
|
||||
|
||||
// See BRepFeat_MakePrism
|
||||
enum PrismMode {
|
||||
CutFromBase = 0,
|
||||
@@ -120,5 +143,6 @@ protected:
|
||||
|
||||
} //namespace PartDesign
|
||||
|
||||
ENABLE_BITMASK_OPERATORS(PartDesign::FeatureExtrude::ExtrudeOption)
|
||||
|
||||
#endif // PARTDESIGN_FEATURE_EXTRUDE_H
|
||||
|
||||
@@ -2000,7 +2000,7 @@ TopoDS_Compound Hole::findHoles(const TopoDS_Shape& profileshape,
|
||||
Handle(Geom_Curve) c = BRep_Tool::Curve(edge, c_start, c_end);
|
||||
|
||||
// Circle?
|
||||
if (c->DynamicType() != STANDARD_TYPE(Geom_Circle)) {
|
||||
if (c.IsNull() || c->DynamicType() != STANDARD_TYPE(Geom_Circle)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,13 @@ Pad::Pad()
|
||||
Length2.setConstraints(nullptr);
|
||||
}
|
||||
|
||||
#ifdef FC_USE_TNP_FIX
|
||||
|
||||
App::DocumentObjectExecReturn* Pad::execute()
|
||||
{
|
||||
return buildExtrusion(ExtrudeOption::MakeFace | ExtrudeOption::MakeFuse);
|
||||
}
|
||||
#else
|
||||
App::DocumentObjectExecReturn *Pad::execute()
|
||||
{
|
||||
double L = Length.getValue();
|
||||
@@ -262,3 +269,4 @@ App::DocumentObjectExecReturn *Pad::execute()
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
|
||||
#include "FeaturePrimitive.h"
|
||||
#include "FeaturePy.h"
|
||||
#include "Mod/Part/App/TopoShapeOpCode.h"
|
||||
|
||||
using namespace PartDesign;
|
||||
|
||||
@@ -65,19 +66,34 @@ FeaturePrimitive::FeaturePrimitive()
|
||||
Part::AttachExtension::initExtension(this);
|
||||
}
|
||||
|
||||
App::DocumentObjectExecReturn* FeaturePrimitive::execute(const TopoDS_Shape& primitiveShape)
|
||||
App::DocumentObjectExecReturn* FeaturePrimitive::execute(const TopoDS_Shape& primitive)
|
||||
{
|
||||
try {
|
||||
//transform the primitive in the correct coordinance
|
||||
FeatureAddSub::execute();
|
||||
|
||||
//if we have no base we just add the standard primitive shape
|
||||
#ifdef FC_USE_TNP_FIX
|
||||
TopoShape primitiveShape;
|
||||
primitiveShape.setShape(primitive);
|
||||
|
||||
TopoShape base;
|
||||
try {
|
||||
// if we have a base shape we need to make sure that it does not get our transformation
|
||||
// to
|
||||
base = getBaseTopoShape().moved(getLocation().Inverted());
|
||||
primitiveShape.Tag = -this->getID();
|
||||
}
|
||||
|
||||
#else
|
||||
auto primitiveShape = primitive;
|
||||
TopoDS_Shape base;
|
||||
try {
|
||||
//if we have a base shape we need to make sure that it does not get our transformation to
|
||||
BRepBuilderAPI_Transform trsf(getBaseShape(), getLocation().Transformation().Inverted(), true);
|
||||
base = trsf.Shape();
|
||||
}
|
||||
#endif
|
||||
catch (const Base::Exception&) {
|
||||
|
||||
//as we use this for preview we can add it even if useless for subtractive
|
||||
@@ -90,14 +106,45 @@ App::DocumentObjectExecReturn* FeaturePrimitive::execute(const TopoDS_Shape& pri
|
||||
|
||||
return App::DocumentObject::StdReturn;
|
||||
}
|
||||
#ifdef FC_USE_TNP_FIX
|
||||
AddSubShape.setValue(primitiveShape);
|
||||
|
||||
TopoShape boolOp(0);
|
||||
|
||||
const char* maker;
|
||||
switch (getAddSubType()) {
|
||||
case Additive:
|
||||
maker = Part::OpCodes::Fuse;
|
||||
break;
|
||||
case Subtractive:
|
||||
maker = Part::OpCodes::Cut;
|
||||
break;
|
||||
default:
|
||||
return new App::DocumentObjectExecReturn(
|
||||
QT_TRANSLATE_NOOP("Exception", "Unknown operation type"));
|
||||
}
|
||||
try {
|
||||
boolOp.makeElementBoolean(maker, {base, primitiveShape});
|
||||
}
|
||||
catch (Standard_Failure& e) {
|
||||
return new App::DocumentObjectExecReturn(
|
||||
QT_TRANSLATE_NOOP("Exception", "Failed to perform boolean operation"));
|
||||
}
|
||||
boolOp = this->getSolid(boolOp);
|
||||
// lets check if the result is a solid
|
||||
if (boolOp.isNull()) {
|
||||
return new App::DocumentObjectExecReturn(
|
||||
QT_TRANSLATE_NOOP("Exception", "Resulting shape is not a solid"));
|
||||
}
|
||||
#else
|
||||
TopoDS_Shape boolOp;
|
||||
if (getAddSubType() == FeatureAddSub::Additive) {
|
||||
|
||||
BRepAlgoAPI_Fuse mkFuse(base, primitiveShape);
|
||||
if (!mkFuse.IsDone())
|
||||
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Adding the primitive failed"));
|
||||
// we have to get the solids (fuse sometimes creates compounds)
|
||||
TopoDS_Shape boolOp = this->getSolid(mkFuse.Shape());
|
||||
boolOp = this->getSolid(mkFuse.Shape());
|
||||
// lets check if the result is a solid
|
||||
if (boolOp.IsNull())
|
||||
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Resulting shape is not a solid"));
|
||||
@@ -106,10 +153,6 @@ App::DocumentObjectExecReturn* FeaturePrimitive::execute(const TopoDS_Shape& pri
|
||||
if (solidCount > 1) {
|
||||
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Result has multiple solids: that is not currently supported."));
|
||||
}
|
||||
|
||||
boolOp = refineShapeIfActive(boolOp);
|
||||
Shape.setValue(getSolid(boolOp));
|
||||
AddSubShape.setValue(primitiveShape);
|
||||
}
|
||||
else if (getAddSubType() == FeatureAddSub::Subtractive) {
|
||||
|
||||
@@ -117,7 +160,7 @@ App::DocumentObjectExecReturn* FeaturePrimitive::execute(const TopoDS_Shape& pri
|
||||
if (!mkCut.IsDone())
|
||||
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Subtracting the primitive failed"));
|
||||
// we have to get the solids (fuse sometimes creates compounds)
|
||||
TopoDS_Shape boolOp = this->getSolid(mkCut.Shape());
|
||||
boolOp = this->getSolid(mkCut.Shape());
|
||||
// lets check if the result is a solid
|
||||
if (boolOp.IsNull())
|
||||
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Resulting shape is not a solid"));
|
||||
@@ -126,13 +169,11 @@ App::DocumentObjectExecReturn* FeaturePrimitive::execute(const TopoDS_Shape& pri
|
||||
if (solidCount > 1) {
|
||||
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Result has multiple solids: that is not currently supported."));
|
||||
}
|
||||
|
||||
boolOp = refineShapeIfActive(boolOp);
|
||||
Shape.setValue(getSolid(boolOp));
|
||||
AddSubShape.setValue(primitiveShape);
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
boolOp = refineShapeIfActive(boolOp);
|
||||
Shape.setValue(getSolid(boolOp));
|
||||
AddSubShape.setValue(primitiveShape);
|
||||
}
|
||||
catch (Standard_Failure& e) {
|
||||
|
||||
|
||||
@@ -62,6 +62,8 @@
|
||||
#include "DatumPlane.h"
|
||||
|
||||
|
||||
FC_LOG_LEVEL_INIT("PartDesign",true,true);
|
||||
|
||||
using namespace PartDesign;
|
||||
|
||||
PROPERTY_SOURCE(PartDesign::ProfileBased, PartDesign::FeatureAddSub)
|
||||
@@ -163,6 +165,29 @@ Part::Feature* ProfileBased::getVerifiedObject(bool silent) const {
|
||||
return static_cast<Part::Feature*>(result);
|
||||
}
|
||||
|
||||
#ifdef FC_USE_TNP_FIX
|
||||
TopoShape ProfileBased::getProfileShape() const
|
||||
{
|
||||
TopoShape shape;
|
||||
const auto& subs = Profile.getSubValues();
|
||||
auto profile = Profile.getValue();
|
||||
if (subs.empty()) {
|
||||
shape = Part::Feature::getTopoShape(profile);
|
||||
}
|
||||
else {
|
||||
std::vector<TopoShape> shapes;
|
||||
for (auto& sub : subs) {
|
||||
shapes.push_back(
|
||||
Part::Feature::getTopoShape(profile, sub.c_str(), /* needSubElement */ true));
|
||||
}
|
||||
shape = TopoShape(shape.Tag).makeElementCompound(shapes);
|
||||
}
|
||||
if (shape.isNull()) {
|
||||
throw Part::NullShapeException("Linked shape object is empty");
|
||||
}
|
||||
return shape;
|
||||
}
|
||||
#else
|
||||
Part::TopoShape ProfileBased::getProfileShape() const
|
||||
{
|
||||
auto shape = getTopoShape(Profile.getValue());
|
||||
@@ -174,7 +199,8 @@ Part::TopoShape ProfileBased::getProfileShape() const
|
||||
}
|
||||
return shape;
|
||||
}
|
||||
|
||||
#endif
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
TopoDS_Shape ProfileBased::getVerifiedFace(bool silent) const {
|
||||
|
||||
App::DocumentObject* result = Profile.getValue();
|
||||
@@ -253,7 +279,167 @@ TopoDS_Shape ProfileBased::getVerifiedFace(bool silent) const {
|
||||
return TopoDS_Face();
|
||||
}
|
||||
|
||||
TopoShape ProfileBased::getTopoShapeVerifiedFace(bool silent,
|
||||
bool doFit,
|
||||
bool allowOpen,
|
||||
const App::DocumentObject* profile,
|
||||
const std::vector<std::string>& _subs) const
|
||||
{
|
||||
auto obj = profile ? profile : Profile.getValue();
|
||||
if (!obj || !obj->getNameInDocument()) {
|
||||
if (silent) {
|
||||
return TopoShape();
|
||||
}
|
||||
throw Base::ValueError("No profile linked");
|
||||
}
|
||||
const auto& subs = profile ? _subs : Profile.getSubValues();
|
||||
try {
|
||||
TopoShape shape;
|
||||
if (AllowMultiFace.getValue()) {
|
||||
if (subs.empty()) {
|
||||
shape = Part::Feature::getTopoShape(obj);
|
||||
}
|
||||
else {
|
||||
std::vector<TopoShape> shapes;
|
||||
for (auto& sub : subs) {
|
||||
auto subshape =
|
||||
Part::Feature::getTopoShape(obj, sub.c_str(), /*needSubElement*/ true);
|
||||
if (subshape.isNull()) {
|
||||
FC_THROWM(Base::CADKernelError,
|
||||
"Sub shape not found: " << obj->getFullName() << "." << sub);
|
||||
}
|
||||
shapes.push_back(subshape);
|
||||
}
|
||||
shape.makeElementCompound(shapes);
|
||||
}
|
||||
}
|
||||
else {
|
||||
std::string sub;
|
||||
if (!obj->getTypeId().isDerivedFrom(Part::Part2DObject::getClassTypeId())) {
|
||||
if (!subs.empty()) {
|
||||
sub = subs[0];
|
||||
}
|
||||
}
|
||||
shape = Part::Feature::getTopoShape(obj, sub.c_str(), !sub.empty());
|
||||
}
|
||||
if (shape.isNull()) {
|
||||
if (silent) {
|
||||
return shape;
|
||||
}
|
||||
throw Base::CADKernelError("Linked shape object is empty");
|
||||
}
|
||||
TopoShape openshape;
|
||||
if (!shape.hasSubShape(TopAbs_FACE)) {
|
||||
try {
|
||||
if (!shape.hasSubShape(TopAbs_WIRE)) {
|
||||
shape = shape.makeElementWires();
|
||||
}
|
||||
if (shape.hasSubShape(TopAbs_WIRE)) {
|
||||
shape.Hasher = getDocument()->getStringHasher();
|
||||
if (allowOpen) {
|
||||
std::vector<TopoShape> openwires;
|
||||
std::vector<TopoShape> wires;
|
||||
for (auto& wire : shape.getSubTopoShapes(TopAbs_WIRE)) {
|
||||
if (!wire.isClosed()) {
|
||||
openwires.push_back(wire);
|
||||
}
|
||||
else {
|
||||
wires.push_back(wire);
|
||||
}
|
||||
}
|
||||
if (openwires.size()) {
|
||||
openshape.makeElementCompound(
|
||||
openwires,
|
||||
nullptr,
|
||||
TopoShape ::SingleShapeCompoundCreationPolicy::returnShape);
|
||||
if (wires.empty()) {
|
||||
shape = TopoShape();
|
||||
}
|
||||
else {
|
||||
shape.makeElementCompound(
|
||||
wires,
|
||||
nullptr,
|
||||
TopoShape ::SingleShapeCompoundCreationPolicy::returnShape);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!shape.isNull()) {
|
||||
if (AllowMultiFace.getValue()) {
|
||||
shape = shape.makeElementFace(); // default to use FaceMakerBullseye
|
||||
}
|
||||
else {
|
||||
shape = shape.makeElementFace(nullptr, "Part::FaceMakerCheese");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const Base::Exception&) {
|
||||
if (silent) {
|
||||
return TopoShape();
|
||||
}
|
||||
throw;
|
||||
}
|
||||
catch (const Standard_Failure&) {
|
||||
if (silent) {
|
||||
return TopoShape();
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
int count = shape.countSubShapes(TopAbs_FACE);
|
||||
if (!count && !allowOpen) {
|
||||
if (silent) {
|
||||
return TopoShape();
|
||||
}
|
||||
throw Base::CADKernelError("Cannot make face from profile");
|
||||
}
|
||||
|
||||
// Toponaming April 2024: This appears to be new feature, not TNP:
|
||||
// if (doFit && (std::abs(Fit.getValue()) > Precision::Confusion()
|
||||
// || std::abs(InnerFit.getValue()) > Precision::Confusion())) {
|
||||
//
|
||||
// if (!shape.isNull())
|
||||
// shape = shape.makEOffsetFace(Fit.getValue(),
|
||||
// InnerFit.getValue(),
|
||||
// static_cast<Part::TopoShape::JoinType>(FitJoin.getValue()),
|
||||
// static_cast<Part::TopoShape::JoinType>(InnerFitJoin.getValue()));
|
||||
// if (!openshape.isNull())
|
||||
// openshape.makEOffset2D(Fit.getValue());
|
||||
// }
|
||||
|
||||
if (!openshape.isNull()) {
|
||||
if (shape.isNull()) {
|
||||
shape = openshape;
|
||||
}
|
||||
else {
|
||||
shape.makeElementCompound({shape, openshape});
|
||||
}
|
||||
}
|
||||
if (count > 1) {
|
||||
if (AllowMultiFace.getValue()
|
||||
|| obj->isDerivedFrom(Part::Part2DObject::getClassTypeId())) {
|
||||
return shape;
|
||||
}
|
||||
FC_WARN("Found more than one face from profile");
|
||||
}
|
||||
if (!openshape.isNull()) {
|
||||
return shape;
|
||||
}
|
||||
if (count) {
|
||||
return shape.getSubTopoShape(TopAbs_FACE, 1);
|
||||
}
|
||||
return shape;
|
||||
}
|
||||
catch (Standard_Failure&) {
|
||||
if (silent) {
|
||||
return TopoShape();
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
std::vector<TopoDS_Wire> ProfileBased::getProfileWires() const {
|
||||
std::vector<TopoDS_Wire> result;
|
||||
|
||||
@@ -292,9 +478,29 @@ std::vector<TopoDS_Wire> ProfileBased::getProfileWires() const {
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<TopoShape> ProfileBased::getTopoShapeProfileWires() const
|
||||
{
|
||||
// shape copy is a workaround for an obscure OCC bug which leads to empty
|
||||
// tessellations for some faces. Making an explicit copy of the linked
|
||||
// shape seems to fix it. The error mostly happens when re-computing the
|
||||
// shape but sometimes also for the first time
|
||||
auto shape = getProfileShape().makeElementCopy();
|
||||
|
||||
if (shape.hasSubShape(TopAbs_WIRE)) {
|
||||
return shape.getSubTopoShapes(TopAbs_WIRE);
|
||||
}
|
||||
|
||||
auto wires = shape.makeElementWires().getSubTopoShapes(TopAbs_WIRE);
|
||||
if (wires.empty()) {
|
||||
throw Part::NullShapeException("Linked shape object is not a wire");
|
||||
}
|
||||
return wires;
|
||||
}
|
||||
|
||||
// Note: We cannot return a reference, because it will become Null.
|
||||
// Not clear where, because we check for IsNull() here, but as soon as it is passed out of
|
||||
// this method, it becomes null!
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
const TopoDS_Face ProfileBased::getSupportFace() const
|
||||
{
|
||||
const Part::Part2DObject* sketch = getVerifiedSketch(true);
|
||||
@@ -307,7 +513,8 @@ const TopoDS_Face ProfileBased::getSupportFace() const
|
||||
|
||||
TopoDS_Face ProfileBased::getSupportFace(const Part::Part2DObject* sketch) const
|
||||
{
|
||||
if (sketch && sketch->MapMode.getValue() == Attacher::mmFlatFace && sketch->AttachmentSupport.getValue()) {
|
||||
if (sketch && sketch->MapMode.getValue() == Attacher::mmFlatFace
|
||||
&& sketch->AttachmentSupport.getValue()) {
|
||||
const auto& AttachmentSupport = sketch->AttachmentSupport;
|
||||
App::DocumentObject* ref = AttachmentSupport.getValue();
|
||||
|
||||
@@ -323,20 +530,24 @@ TopoDS_Face ProfileBased::getSupportFace(const Part::Part2DObject* sketch) const
|
||||
|
||||
// get the selected sub shape (a Face)
|
||||
const Part::TopoShape& shape = part->Shape.getShape();
|
||||
if (shape.getShape().IsNull())
|
||||
if (shape.getShape().IsNull()) {
|
||||
throw Base::ValueError("Sketch support shape is empty!");
|
||||
}
|
||||
|
||||
TopoDS_Shape sh = shape.getSubShape(sub[0].c_str());
|
||||
if (sh.IsNull())
|
||||
if (sh.IsNull()) {
|
||||
throw Base::ValueError("Null shape in SketchBased::getSupportFace()!");
|
||||
}
|
||||
|
||||
const TopoDS_Face face = TopoDS::Face(sh);
|
||||
if (face.IsNull())
|
||||
if (face.IsNull()) {
|
||||
throw Base::ValueError("Null face in SketchBased::getSupportFace()!");
|
||||
}
|
||||
|
||||
BRepAdaptor_Surface adapt(face);
|
||||
if (adapt.GetType() != GeomAbs_Plane)
|
||||
if (adapt.GetType() != GeomAbs_Plane) {
|
||||
throw Base::TypeError("No planar face in SketchBased::getSupportFace()!");
|
||||
}
|
||||
|
||||
return face;
|
||||
}
|
||||
@@ -356,6 +567,42 @@ TopoDS_Face ProfileBased::getSupportFace(const App::PropertyLinkSub& link) const
|
||||
return face;
|
||||
}
|
||||
|
||||
TopoShape ProfileBased::getTopoShapeSupportFace() const
|
||||
{
|
||||
TopoShape shape;
|
||||
const Part::Part2DObject* sketch = getVerifiedSketch(true);
|
||||
if (!sketch) {
|
||||
shape = getTopoShapeVerifiedFace();
|
||||
}
|
||||
else if (sketch->MapMode.getValue() == Attacher::mmFlatFace
|
||||
&& sketch->AttachmentSupport.getValue()) {
|
||||
const auto& Support = sketch->AttachmentSupport;
|
||||
App::DocumentObject* ref = Support.getValue();
|
||||
shape = Part::Feature::getTopoShape(
|
||||
ref,
|
||||
Support.getSubValues().size() ? Support.getSubValues()[0].c_str() : "",
|
||||
true);
|
||||
}
|
||||
if (!shape.isNull()) {
|
||||
if (shape.shapeType(true) != TopAbs_FACE) {
|
||||
if (!shape.hasSubShape(TopAbs_FACE)) {
|
||||
throw Base::ValueError("Null face in SketchBased::getSupportFace()!");
|
||||
}
|
||||
shape = shape.getSubTopoShape(TopAbs_FACE, 1);
|
||||
}
|
||||
gp_Pln pln;
|
||||
if (!shape.findPlane(pln)) {
|
||||
throw Base::TypeError("No planar face in SketchBased::getSupportFace()!");
|
||||
}
|
||||
|
||||
return shape;
|
||||
}
|
||||
if (!sketch) {
|
||||
throw Base::RuntimeError("No planar support");
|
||||
}
|
||||
return Feature::makeShapeFromPlane(sketch);
|
||||
}
|
||||
|
||||
int ProfileBased::getSketchAxisCount() const
|
||||
{
|
||||
Part::Part2DObject* sketch = static_cast<Part::Part2DObject*>(Profile.getValue());
|
||||
@@ -415,6 +662,26 @@ void ProfileBased::onChanged(const App::Property* prop)
|
||||
FeatureAddSub::onChanged(prop);
|
||||
}
|
||||
|
||||
void ProfileBased::getUpToFaceFromLinkSub(TopoShape& upToFace, const App::PropertyLinkSub& refFace)
|
||||
{
|
||||
App::DocumentObject* ref = refFace.getValue();
|
||||
|
||||
if (!ref) {
|
||||
throw Base::ValueError("SketchBased: No face selected");
|
||||
}
|
||||
|
||||
if (ref->getTypeId().isDerivedFrom(App::Plane::getClassTypeId())) {
|
||||
upToFace = makeShapeFromPlane(ref);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& subs = refFace.getSubValues();
|
||||
upToFace = Part::Feature::getTopoShape(ref, subs.size() ? subs[0].c_str() : nullptr, true);
|
||||
if (!upToFace.hasSubShape(TopAbs_FACE)) {
|
||||
throw Base::ValueError("SketchBased: Up to face: Failed to extract face");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ProfileBased::getFaceFromLinkSub(TopoDS_Face& upToFace, const App::PropertyLinkSub& refFace)
|
||||
{
|
||||
@@ -447,6 +714,7 @@ void ProfileBased::getFaceFromLinkSub(TopoDS_Face& upToFace, const App::Property
|
||||
throw Base::ValueError("SketchBased: Failed to extract face");
|
||||
}
|
||||
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
void ProfileBased::getUpToFace(TopoDS_Face& upToFace,
|
||||
const TopoDS_Shape& support,
|
||||
const TopoDS_Shape& sketchshape,
|
||||
@@ -540,6 +808,67 @@ void ProfileBased::getUpToFace(TopoDS_Face& upToFace,
|
||||
}
|
||||
}
|
||||
|
||||
void ProfileBased::getUpToFace(TopoShape& upToFace,
|
||||
const TopoShape& support,
|
||||
const TopoShape& supportface,
|
||||
const TopoShape& sketchshape,
|
||||
const std::string& method,
|
||||
gp_Dir& dir)
|
||||
{
|
||||
if ((method == "UpToLast") || (method == "UpToFirst")) {
|
||||
std::vector<Part::cutTopoShapeFaces> cfaces =
|
||||
Part::findAllFacesCutBy(support, sketchshape, dir);
|
||||
if (cfaces.empty()) {
|
||||
throw Base::ValueError("SketchBased: No faces found in this direction");
|
||||
}
|
||||
|
||||
// Find nearest/furthest face
|
||||
std::vector<Part::cutTopoShapeFaces>::const_iterator it, it_near, it_far;
|
||||
it_near = it_far = cfaces.begin();
|
||||
for (it = cfaces.begin(); it != cfaces.end(); it++) {
|
||||
if (it->distsq > it_far->distsq) {
|
||||
it_far = it;
|
||||
}
|
||||
else if (it->distsq < it_near->distsq) {
|
||||
it_near = it;
|
||||
}
|
||||
}
|
||||
upToFace = (method == "UpToLast" ? it_far->face : it_near->face);
|
||||
}
|
||||
else if (Part::findAllFacesCutBy(upToFace, sketchshape, dir).empty()) {
|
||||
dir = -dir;
|
||||
}
|
||||
|
||||
if (upToFace.shapeType(true) != TopAbs_FACE) {
|
||||
if (!upToFace.hasSubShape(TopAbs_FACE)) {
|
||||
throw Base::ValueError("SketchBased: Up to face: No face found");
|
||||
}
|
||||
upToFace = upToFace.getSubTopoShape(TopAbs_FACE, 1);
|
||||
}
|
||||
|
||||
TopoDS_Face face = TopoDS::Face(upToFace.getShape());
|
||||
|
||||
// Check that the upToFace does not intersect the sketch face and
|
||||
// is not parallel to the extrusion direction (for simplicity, supportface is used instead of
|
||||
// sketchshape)
|
||||
BRepAdaptor_Surface adapt1(TopoDS::Face(supportface.getShape()));
|
||||
BRepAdaptor_Surface adapt2(face);
|
||||
|
||||
if (adapt2.GetType() == GeomAbs_Plane) {
|
||||
if (adapt1.Plane().Axis().IsNormal(adapt2.Plane().Axis(), Precision::Confusion())) {
|
||||
throw Base::ValueError(
|
||||
"SketchBased: Up to face: Must not be parallel to extrusion direction!");
|
||||
}
|
||||
}
|
||||
|
||||
// We must measure from sketchshape, not supportface, here
|
||||
BRepExtrema_DistShapeShape distSS(sketchshape.getShape(), face);
|
||||
if (distSS.Value() < Precision::Confusion()) {
|
||||
throw Base::ValueError("SketchBased: Up to face: Must not intersect sketch!");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
void ProfileBased::addOffsetToFace(TopoDS_Face& upToFace, const gp_Dir& dir, double offset)
|
||||
{
|
||||
// Move the face in the extrusion direction
|
||||
@@ -564,6 +893,18 @@ void ProfileBased::addOffsetToFace(TopoDS_Face& upToFace, const gp_Dir& dir, dou
|
||||
}
|
||||
}
|
||||
|
||||
void ProfileBased::addOffsetToFace(TopoShape& upToFace, const gp_Dir& dir, double offset)
|
||||
{
|
||||
// Move the face in the extrusion direction
|
||||
// TODO: For non-planar faces, we could consider offsetting the surface
|
||||
if (fabs(offset) > Precision::Confusion()) {
|
||||
gp_Trsf mov;
|
||||
mov.SetTranslation(offset * gp_Vec(dir));
|
||||
TopLoc_Location loc(mov);
|
||||
upToFace.move(loc);
|
||||
}
|
||||
}
|
||||
|
||||
double ProfileBased::getThroughAllLength() const
|
||||
{
|
||||
TopoDS_Shape profileshape;
|
||||
@@ -739,6 +1080,13 @@ bool ProfileBased::checkLineCrossesFace(const gp_Lin& line, const TopoDS_Face& f
|
||||
|
||||
void ProfileBased::remapSupportShape(const TopoDS_Shape & newShape)
|
||||
{
|
||||
#if FC_USE_TNP_FIX
|
||||
(void)newShape;
|
||||
// Realthunder: with the new topological naming, I don't think this function
|
||||
// is necessary. A missing element will cause an explicitly error, and the
|
||||
// user will be force to manually select the element. Various editors, such
|
||||
// as dress up editors, can perform element guessing when activated.
|
||||
#else
|
||||
TopTools_IndexedMapOfShape faceMap;
|
||||
TopExp::MapShapes(newShape, TopAbs_FACE, faceMap);
|
||||
|
||||
@@ -830,6 +1178,7 @@ void ProfileBased::remapSupportShape(const TopoDS_Shape & newShape)
|
||||
link->setValue(this, newSubValues);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace PartDesign {
|
||||
|
||||
@@ -97,17 +97,39 @@ public:
|
||||
* silently returns nullptr, otherwise throw a Base::Exception.
|
||||
* Default is false.
|
||||
*/
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
TopoDS_Shape getVerifiedFace(bool silent = false) const;
|
||||
|
||||
/**
|
||||
* Verifies the linked Object and returns the shape used as profile
|
||||
* @param silent: if profile property is malformed and the parameter is true
|
||||
* silently returns nullptr, otherwise throw a Base::Exception.
|
||||
* Default is false.
|
||||
* @param doFit: Whether to fitting according to the 'Fit' property
|
||||
* @param allowOpen: Whether allow open wire
|
||||
* @param profile: optional profile object, if not given then use 'Profile' property
|
||||
* @param subs: optional profile sub-object names, if not given then use 'Profile' property
|
||||
*/
|
||||
TopoShape getTopoShapeVerifiedFace(bool silent = false,
|
||||
bool doFit = true,
|
||||
bool allowOpen = false,
|
||||
const App::DocumentObject* profile = nullptr,
|
||||
const std::vector<std::string>& subs = {}) const;
|
||||
|
||||
/// Returns the wires the sketch is composed of
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
std::vector<TopoDS_Wire> getProfileWires() const;
|
||||
std::vector<TopoShape> getTopoShapeProfileWires() const;
|
||||
|
||||
|
||||
/// Returns the face of the sketch support (if any)
|
||||
const TopoDS_Face getSupportFace() const;
|
||||
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
|
||||
TopoShape getTopoShapeSupportFace() const;
|
||||
|
||||
Base::Vector3d getProfileNormal() const;
|
||||
|
||||
Part::TopoShape getProfileShape() const;
|
||||
TopoShape getProfileShape() const;
|
||||
|
||||
/// retrieves the number of axes in the linked sketch (defined as construction lines)
|
||||
int getSketchAxisCount() const;
|
||||
@@ -128,8 +150,7 @@ protected:
|
||||
TopoDS_Face getSupportFace(const App::PropertyLinkSub& link) const;
|
||||
|
||||
/// Extract a face from a given LinkSub
|
||||
static void getFaceFromLinkSub(TopoDS_Face& upToFace,
|
||||
const App::PropertyLinkSub& refFace);
|
||||
static void getFaceFromLinkSub(TopoDS_Face& upToFace, const App::PropertyLinkSub& refFace);
|
||||
|
||||
/// Find a valid face to extrude up to
|
||||
static void getUpToFace(TopoDS_Face& upToFace,
|
||||
@@ -139,7 +160,20 @@ protected:
|
||||
const gp_Dir& dir);
|
||||
|
||||
/// Add an offset to the face
|
||||
static void addOffsetToFace(TopoDS_Face& upToFace,
|
||||
static void addOffsetToFace(TopoDS_Face& upToFace, const gp_Dir& dir, double offset);
|
||||
/// Extract a face from a given LinkSub
|
||||
static void getUpToFaceFromLinkSub(TopoShape& upToFace, const App::PropertyLinkSub& refFace);
|
||||
|
||||
/// Find a valid face to extrude up to
|
||||
static void getUpToFace(TopoShape& upToFace,
|
||||
const TopoShape& support,
|
||||
const TopoShape& supportface,
|
||||
const TopoShape& sketchshape,
|
||||
const std::string& method,
|
||||
gp_Dir& dir);
|
||||
|
||||
/// Add an offset to the face
|
||||
static void addOffsetToFace(TopoShape& upToFace,
|
||||
const gp_Dir& dir,
|
||||
double offset);
|
||||
|
||||
|
||||
@@ -110,11 +110,38 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
else:
|
||||
print("TOPOLOGICAL NAMING PROBLEM IS PRESENT.")
|
||||
|
||||
def testPartDesignElementMapPad(self):
|
||||
""" Test that padding a sketch results in a correct element map. Note that comprehensive testing
|
||||
of the geometric functionality of the Pad is in TestPad.py """
|
||||
# Arrange
|
||||
body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
padSketch = self.Doc.addObject('Sketcher::SketchObject', 'SketchPad')
|
||||
pad = self.Doc.addObject("PartDesign::Pad", "Pad")
|
||||
body.addObject(padSketch)
|
||||
body.addObject(pad)
|
||||
TestSketcherApp.CreateRectangleSketch(padSketch, (0, 0), (1, 1))
|
||||
pad.Profile = padSketch
|
||||
pad.Length = 1
|
||||
# Act
|
||||
self.Doc.recompute()
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
reverseMap = pad.Shape.ElementReverseMap
|
||||
faces = [name for name in reverseMap.keys() if name.startswith("Face")]
|
||||
edges = [name for name in reverseMap.keys() if name.startswith("Edge")]
|
||||
vertexes = [name for name in reverseMap.keys() if name.startswith("Vertex")]
|
||||
# Assert
|
||||
self.assertEqual(pad.Shape.ElementMapSize,30) # 4 duplicated Vertexes in here
|
||||
self.assertEqual(len(reverseMap),26)
|
||||
self.assertEqual(len(faces),6)
|
||||
self.assertEqual(len(edges),12)
|
||||
self.assertEqual(len(vertexes),8)
|
||||
|
||||
def testPartDesignElementMapBox(self):
|
||||
# Arrange
|
||||
body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
box = self.Doc.addObject('PartDesign::AdditiveBox', 'Box')
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act / Assert
|
||||
self.assertEqual(len(box.Shape.childShapes()), 0)
|
||||
@@ -131,7 +158,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
# Arrange
|
||||
body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
cylinder = self.Doc.addObject('PartDesign::AdditiveCylinder', 'Cylinder')
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act / Assert
|
||||
self.assertEqual(len(cylinder.Shape.childShapes()), 0)
|
||||
@@ -141,14 +168,22 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
body.addObject(cylinder)
|
||||
self.assertEqual(len(body.Shape.childShapes()), 0)
|
||||
self.Doc.recompute()
|
||||
reverseMap = body.Shape.childShapes()[0].ElementReverseMap
|
||||
faces = [name for name in reverseMap.keys() if name.startswith("Face")]
|
||||
edges = [name for name in reverseMap.keys() if name.startswith("Edge")]
|
||||
vertexes = [name for name in reverseMap.keys() if name.startswith("Vertex")]
|
||||
self.assertEqual(len(body.Shape.childShapes()), 1)
|
||||
self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 8)
|
||||
self.assertEqual(len(reverseMap),8)
|
||||
self.assertEqual(len(faces),3)
|
||||
self.assertEqual(len(edges),3)
|
||||
self.assertEqual(len(vertexes),2)
|
||||
|
||||
def testPartDesignElementMapSphere(self):
|
||||
# Arrange
|
||||
body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
sphere = self.Doc.addObject('PartDesign::AdditiveSphere', 'Sphere')
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act / Assert
|
||||
self.assertEqual(len(sphere.Shape.childShapes()), 0)
|
||||
@@ -158,14 +193,22 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
body.addObject(sphere)
|
||||
self.assertEqual(len(body.Shape.childShapes()), 0)
|
||||
self.Doc.recompute()
|
||||
reverseMap = body.Shape.childShapes()[0].ElementReverseMap
|
||||
faces = [name for name in reverseMap.keys() if name.startswith("Face")]
|
||||
edges = [name for name in reverseMap.keys() if name.startswith("Edge")]
|
||||
vertexes = [name for name in reverseMap.keys() if name.startswith("Vertex")]
|
||||
self.assertEqual(len(body.Shape.childShapes()), 1)
|
||||
self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 6)
|
||||
self.assertEqual(len(reverseMap),6)
|
||||
self.assertEqual(len(faces),1)
|
||||
self.assertEqual(len(edges),3)
|
||||
self.assertEqual(len(vertexes),2)
|
||||
|
||||
def testPartDesignElementMapCone(self):
|
||||
# Arrange
|
||||
body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
cone = self.Doc.addObject('PartDesign::AdditiveCone', 'Cone')
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act / Assert
|
||||
self.assertEqual(len(cone.Shape.childShapes()), 0)
|
||||
@@ -175,14 +218,22 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
body.addObject(cone)
|
||||
self.assertEqual(len(body.Shape.childShapes()), 0)
|
||||
self.Doc.recompute()
|
||||
reverseMap = body.Shape.childShapes()[0].ElementReverseMap
|
||||
faces = [name for name in reverseMap.keys() if name.startswith("Face")]
|
||||
edges = [name for name in reverseMap.keys() if name.startswith("Edge")]
|
||||
vertexes = [name for name in reverseMap.keys() if name.startswith("Vertex")]
|
||||
self.assertEqual(len(body.Shape.childShapes()), 1)
|
||||
self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 8)
|
||||
self.assertEqual(len(reverseMap),8)
|
||||
self.assertEqual(len(faces),3)
|
||||
self.assertEqual(len(edges),3)
|
||||
self.assertEqual(len(vertexes),2)
|
||||
|
||||
def testPartDesignElementMapEllipsoid(self):
|
||||
# Arrange
|
||||
body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
ellipsoid = self.Doc.addObject('PartDesign::AdditiveEllipsoid', 'Ellipsoid')
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act / Assert
|
||||
self.assertEqual(len(ellipsoid.Shape.childShapes()), 0)
|
||||
@@ -192,14 +243,22 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
body.addObject(ellipsoid)
|
||||
self.assertEqual(len(body.Shape.childShapes()), 0)
|
||||
self.Doc.recompute()
|
||||
reverseMap = body.Shape.childShapes()[0].ElementReverseMap
|
||||
faces = [name for name in reverseMap.keys() if name.startswith("Face")]
|
||||
edges = [name for name in reverseMap.keys() if name.startswith("Edge")]
|
||||
vertexes = [name for name in reverseMap.keys() if name.startswith("Vertex")]
|
||||
self.assertEqual(len(body.Shape.childShapes()), 1)
|
||||
self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 6)
|
||||
self.assertEqual(len(reverseMap),6)
|
||||
self.assertEqual(len(faces),1)
|
||||
self.assertEqual(len(edges),3)
|
||||
self.assertEqual(len(vertexes),2)
|
||||
|
||||
def testPartDesignElementMapTorus(self):
|
||||
# Arrange
|
||||
body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
torus = self.Doc.addObject('PartDesign::AdditiveTorus', 'Torus')
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act / Assert
|
||||
self.assertEqual(len(torus.Shape.childShapes()), 0)
|
||||
@@ -209,14 +268,22 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
body.addObject(torus)
|
||||
self.assertEqual(len(body.Shape.childShapes()), 0)
|
||||
self.Doc.recompute()
|
||||
reverseMap = body.Shape.childShapes()[0].ElementReverseMap
|
||||
faces = [name for name in reverseMap.keys() if name.startswith("Face")]
|
||||
edges = [name for name in reverseMap.keys() if name.startswith("Edge")]
|
||||
vertexes = [name for name in reverseMap.keys() if name.startswith("Vertex")]
|
||||
self.assertEqual(len(body.Shape.childShapes()), 1)
|
||||
self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 4)
|
||||
self.assertEqual(len(reverseMap),4)
|
||||
self.assertEqual(len(faces),1)
|
||||
self.assertEqual(len(edges),2)
|
||||
self.assertEqual(len(vertexes),1)
|
||||
|
||||
def testPartDesignElementMapPrism(self):
|
||||
# Arrange
|
||||
body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
prism = self.Doc.addObject('PartDesign::AdditivePrism', 'Prism')
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act / Assert
|
||||
self.assertEqual(len(prism.Shape.childShapes()), 0)
|
||||
@@ -226,14 +293,22 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
body.addObject(prism)
|
||||
self.assertEqual(len(body.Shape.childShapes()), 0)
|
||||
self.Doc.recompute()
|
||||
reverseMap = body.Shape.childShapes()[0].ElementReverseMap
|
||||
faces = [name for name in reverseMap.keys() if name.startswith("Face")]
|
||||
edges = [name for name in reverseMap.keys() if name.startswith("Edge")]
|
||||
vertexes = [name for name in reverseMap.keys() if name.startswith("Vertex")]
|
||||
self.assertEqual(len(body.Shape.childShapes()), 1)
|
||||
self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 38)
|
||||
self.assertEqual(len(reverseMap),38)
|
||||
self.assertEqual(len(faces),8)
|
||||
self.assertEqual(len(edges),18)
|
||||
self.assertEqual(len(vertexes),12)
|
||||
|
||||
def testPartDesignElementMapWedge(self):
|
||||
# Arrange
|
||||
body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
wedge = self.Doc.addObject('PartDesign::AdditiveWedge', 'Wedge')
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act / Assert
|
||||
self.assertEqual(len(wedge.Shape.childShapes()), 0)
|
||||
@@ -243,8 +318,16 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
body.addObject(wedge)
|
||||
self.assertEqual(len(body.Shape.childShapes()), 0)
|
||||
self.Doc.recompute()
|
||||
reverseMap = body.Shape.childShapes()[0].ElementReverseMap
|
||||
faces = [name for name in reverseMap.keys() if name.startswith("Face")]
|
||||
edges = [name for name in reverseMap.keys() if name.startswith("Edge")]
|
||||
vertexes = [name for name in reverseMap.keys() if name.startswith("Vertex")]
|
||||
self.assertEqual(len(body.Shape.childShapes()), 1)
|
||||
self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 26)
|
||||
self.assertEqual(len(reverseMap),26)
|
||||
self.assertEqual(len(faces),6)
|
||||
self.assertEqual(len(edges),12)
|
||||
self.assertEqual(len(vertexes),8)
|
||||
|
||||
# body.BaseFeature = box
|
||||
|
||||
@@ -256,7 +339,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
box.Width = 20
|
||||
box.Height = 20
|
||||
body.addObject(box)
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
subbox = self.Doc.addObject('PartDesign::SubtractiveBox', 'Box')
|
||||
@@ -275,7 +358,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
box.Width = 20
|
||||
box.Height = 20
|
||||
body.addObject(box)
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
subcylinder = self.Doc.addObject('PartDesign::SubtractiveCylinder', 'Cylinder')
|
||||
@@ -294,7 +377,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
box.Width = 20
|
||||
box.Height = 20
|
||||
body.addObject(box)
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
subsphere = self.Doc.addObject('PartDesign::SubtractiveSphere', 'Sphere')
|
||||
@@ -313,7 +396,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
box.Width = 20
|
||||
box.Height = 20
|
||||
body.addObject(box)
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
subcone = self.Doc.addObject('PartDesign::SubtractiveCone', 'Cone')
|
||||
@@ -332,7 +415,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
box.Width = 20
|
||||
box.Height = 20
|
||||
body.addObject(box)
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
subellipsoid = self.Doc.addObject('PartDesign::SubtractiveEllipsoid', 'Ellipsoid')
|
||||
@@ -351,7 +434,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
box.Width = 20
|
||||
box.Height = 20
|
||||
body.addObject(box)
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
subtorus = self.Doc.addObject('PartDesign::SubtractiveTorus', 'Torus')
|
||||
@@ -370,7 +453,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
box.Width = 20
|
||||
box.Height = 20
|
||||
body.addObject(box)
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
subprism = self.Doc.addObject('PartDesign::SubtractivePrism', 'Prism')
|
||||
@@ -389,7 +472,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
box.Width = 20
|
||||
box.Height = 20
|
||||
body.addObject(box)
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
subwedge = self.Doc.addObject('PartDesign::SubtractiveWedge', 'Wedge')
|
||||
@@ -405,25 +488,35 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
sketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch')
|
||||
TestSketcherApp.CreateRectangleSketch(sketch, (0, 0), (1, 1))
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
pad = self.Doc.addObject('PartDesign::Pad', 'Pad')
|
||||
pad.Profile = sketch
|
||||
pad.BaseFeature = sketch
|
||||
body.addObject(sketch)
|
||||
body.addObject(pad)
|
||||
self.Doc.recompute()
|
||||
# Assert
|
||||
self.assertEqual(len(body.Shape.childShapes()), 1)
|
||||
self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 26)
|
||||
# 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.ElementMapSize,30)
|
||||
self.assertEqual(sketch.Shape.ElementMapSize,12)
|
||||
self.assertEqual(pad.Shape.ElementMapSize,30)
|
||||
# Todo: Assert that the names in the ElementMap are good; in particular that they are hashed with a # starting
|
||||
|
||||
def testPartDesignElementMapRevolution(self):
|
||||
# Arrange
|
||||
body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
sketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch')
|
||||
TestSketcherApp.CreateRectangleSketch(sketch, (0, 0), (1, 1))
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
revolution = self.Doc.addObject('PartDesign::Revolution', 'Revolution')
|
||||
@@ -444,7 +537,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
sketch2 = self.Doc.addObject('Sketcher::SketchObject', 'Sketch')
|
||||
TestSketcherApp.CreateRectangleSketch(sketch2, (0, 0), (2, 2))
|
||||
sketch2.Placement.move(App.Vector(0, 0, 3))
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
loft = self.Doc.addObject('PartDesign::AdditiveLoft', 'Loft')
|
||||
@@ -465,7 +558,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
TestSketcherApp.CreateRectangleSketch(sketch, (0, 0), (1, 1))
|
||||
sketch2 = self.Doc.addObject('Sketcher::SketchObject', 'Sketch')
|
||||
TestSketcherApp.CreateRectangleSketch(sketch2, (0, 0), (2, 2))
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
pipe = self.Doc.addObject('PartDesign::AdditivePipe', 'Pipe')
|
||||
@@ -484,7 +577,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
sketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch')
|
||||
TestSketcherApp.CreateRectangleSketch(sketch, (0, 0), (1, 1))
|
||||
if not hasattr(body,"ElementMapVersion"): # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Act
|
||||
helix = self.Doc.addObject('PartDesign::AdditiveHelix', 'Helix')
|
||||
@@ -519,6 +612,8 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
groove.Reversed = 0
|
||||
groove.Base = App.Vector(0, 0, 0)
|
||||
self.Doc.recompute()
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Assert
|
||||
# print(groove.Shape.childShapes()[0].ElementMap)
|
||||
# TODO: Complete me as part of the subtractive features
|
||||
@@ -638,14 +733,13 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
pad.Profile = sketch
|
||||
body.addObject(pad)
|
||||
self.Doc.recompute()
|
||||
if body.Shape.ElementMapVersion == "": # Skip without element maps.
|
||||
if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Assert
|
||||
self.assertEqual(sketch.Shape.ElementMapSize, 12)
|
||||
self.assertEqual(pad.Shape.ElementMapSize, 30) # The sketch plus the pad in the map
|
||||
# TODO: differing results between main and LS3 on these values. Does it matter?
|
||||
# self.assertEqual(body.Shape.ElementMapSize,0) # 8?
|
||||
# self.Doc.recompute()
|
||||
# self.assertEqual(body.Shape.ElementMapSize,30) # 26
|
||||
|
||||
def testPlaneElementMap(self):
|
||||
@@ -657,7 +751,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
pad = self.Doc.addObject('PartDesign::Pad', 'Pad')
|
||||
pad.Profile = plane
|
||||
self.Doc.recompute()
|
||||
if pad.Shape.ElementMapVersion == "": # Skip without element maps.
|
||||
if pad.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
# Assert
|
||||
self.assertEqual(plane.Shape.ElementMapSize, 0)
|
||||
|
||||
@@ -261,6 +261,20 @@ void SketchObject::buildShape()
|
||||
if(GeometryFacade::getConstruction(geo))
|
||||
continue;
|
||||
if (geo->isDerivedFrom(Part::GeomPoint::getClassTypeId())) {
|
||||
#ifdef FC_USE_TNP_FIX
|
||||
Part::TopoShape vertex(TopoDS::Vertex(geo->toShape()));
|
||||
int idx = getVertexIndexGeoPos(i-1, Sketcher::PointPos::start);
|
||||
std::string name = convertSubName(Data::IndexedName::fromConst("Vertex", idx+1), false);
|
||||
vertex.setElementName(Data::IndexedName::fromConst("Vertex", 1),
|
||||
Data::MappedName::fromRawData(name.c_str()),0L);
|
||||
vertices.push_back(vertex);
|
||||
vertices.back().copyElementMap(vertex, Part::OpCodes::Sketch);
|
||||
} else {
|
||||
auto indexedName = Data::IndexedName::fromConst("Edge", i);
|
||||
shapes.push_back(getEdge(geo,convertSubName(indexedName, false).c_str()));
|
||||
}
|
||||
|
||||
#else
|
||||
vertices.emplace_back(TopoDS::Vertex(geo->toShape()));
|
||||
int idx = getVertexIndexGeoPos(i-1, PointPos::start);
|
||||
std::string name = convertSubName(Data::IndexedName::fromConst("Vertex", idx+1), false);
|
||||
@@ -269,6 +283,7 @@ void SketchObject::buildShape()
|
||||
} else
|
||||
shapes.push_back(getEdge(geo,convertSubName(
|
||||
Data::IndexedName::fromConst("Edge", i), false).c_str()));
|
||||
#endif
|
||||
}
|
||||
|
||||
// FIXME: Commented since ExternalGeometryFacade is not added
|
||||
@@ -280,11 +295,14 @@ void SketchObject::buildShape()
|
||||
// shapes.push_back(getEdge(geo, convertSubName(
|
||||
// Data::IndexedName::fromConst("ExternalEdge", i-1), false).c_str()));
|
||||
// }
|
||||
if(shapes.empty() && vertices.empty())
|
||||
Shape.setValue(Part::TopoShape());
|
||||
else if (vertices.empty()) {
|
||||
if(shapes.empty() && vertices.empty()) {
|
||||
Shape.setValue(Part::TopoShape());
|
||||
return;
|
||||
}
|
||||
Part::TopoShape result(0, getDocument()->getStringHasher());
|
||||
if (vertices.empty()) {
|
||||
// Notice here we supply op code Part::OpCodes::Sketch to makEWires().
|
||||
Shape.setValue(Part::TopoShape().makeElementWires(shapes,Part::OpCodes::Sketch));
|
||||
result.makeElementWires(shapes,Part::OpCodes::Sketch);
|
||||
} else {
|
||||
std::vector<Part::TopoShape> results;
|
||||
if (!shapes.empty()) {
|
||||
@@ -302,8 +320,10 @@ void SketchObject::buildShape()
|
||||
results.push_back(wire);
|
||||
}
|
||||
results.insert(results.end(), vertices.begin(), vertices.end());
|
||||
Shape.setValue(Part::TopoShape().makeElementCompound(results, Part::OpCodes::Sketch));
|
||||
result.makeElementCompound(results, Part::OpCodes::Sketch);
|
||||
}
|
||||
result.Tag = getID();
|
||||
Shape.setValue(result);
|
||||
}
|
||||
|
||||
static const char *hasSketchMarker(const char *name) {
|
||||
|
||||
@@ -224,6 +224,10 @@ TopoDS_Shape DrawBrokenView::apply1Break(const App::DocumentObject& breakObj, co
|
||||
builder.MakeCompound(result);
|
||||
builder.Add(result, cut0);
|
||||
builder.Add(result, cut1);
|
||||
|
||||
// BRepTools::Write(cut0, "DBVcut0.brep"); //debug
|
||||
// BRepTools::Write(cut1, "DBVcut1.brep");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -241,32 +245,27 @@ TopoDS_Shape DrawBrokenView::compressShape(const TopoDS_Shape& shapeToCompress)
|
||||
//! move the broken pieces in the input shape "right" to close up the removed areas.
|
||||
//! note: breaks and pieces should not intersect by this point
|
||||
//! a break: BbbbbbbB
|
||||
//! a piece: PpppP no need to move
|
||||
//! a piece: PppppP move right by removed(B)
|
||||
//! a piece: PpppP move right by removed(B)
|
||||
//! a piece: PppppP no need to move
|
||||
TopoDS_Shape DrawBrokenView::compressHorizontal(const TopoDS_Shape& shapeToCompress)const
|
||||
{
|
||||
// Base::Console().Message("DBV::compressHorizontal()\n");
|
||||
auto pieces = getPieces(shapeToCompress);
|
||||
auto breaksAll = Breaks.getValues();
|
||||
// ?? not sure about using closestBasis here.
|
||||
auto moveDirection = DU::closestBasis(DU::toVector3d(getProjectionCS().XDirection()));
|
||||
auto moveDirection = DU::closestBasisOriented(DU::toVector3d(getProjectionCS().XDirection()));
|
||||
bool descend = false;
|
||||
auto sortedBreaks = makeSortedBreakList(breaksAll, moveDirection, descend);
|
||||
auto limits = getPieceUpperLimits(pieces, moveDirection);
|
||||
auto limits = getPieceLimits(pieces, moveDirection);
|
||||
// for each break, move all the pieces left of the break to the right by the removed amount
|
||||
// for the break
|
||||
for (auto& breakItem : sortedBreaks) {
|
||||
// check each break against all the pieces
|
||||
Base::Vector3d netBreakDisplace = moveDirection * (removedLengthFromObj(*breakItem.breakObj) - Gap.getValue());
|
||||
Base::Vector3d netBreakDisplace =
|
||||
moveDirection * (removedLengthFromObj(*breakItem.breakObj) - Gap.getValue());
|
||||
size_t iPiece{0};
|
||||
for (auto& pieceHighLimit : limits) {
|
||||
for (auto& pieceLimit : limits) {
|
||||
// check each piece against the current break
|
||||
// We have a problem with low digits here. The cut operations and later
|
||||
// bounding box creation may generate pieceHighLimits that are slightly
|
||||
// off. We know that the pieces were cut by a break, so we use a fuzzy
|
||||
// comparison.
|
||||
if (pieceHighLimit < breakItem.lowLimit ||
|
||||
DU::fpCompare(pieceHighLimit, breakItem.lowLimit, Precision::Confusion()) ) {
|
||||
if (moveThisPiece(pieceLimit, breakItem, moveDirection)) {
|
||||
// piece is to left of break, so needs to move right
|
||||
TopoDS_Shape temp = ShapeUtils::moveShape(pieces.at(iPiece), netBreakDisplace);
|
||||
pieces.at(iPiece) = temp;
|
||||
@@ -293,20 +292,21 @@ TopoDS_Shape DrawBrokenView::compressVertical(const TopoDS_Shape& shapeToCompre
|
||||
auto pieces = getPieces(shapeToCompress);
|
||||
auto breaksAll = Breaks.getValues();
|
||||
// not sure about using closestBasis here. may prevent oblique breaks later.
|
||||
auto moveDirection = DU::closestBasis(DU::toVector3d(getProjectionCS().YDirection()));
|
||||
auto moveDirection = DU::closestBasisOriented(DU::toVector3d(getProjectionCS().YDirection()));
|
||||
|
||||
bool descend = false;
|
||||
auto sortedBreaks = makeSortedBreakList(breaksAll, moveDirection, descend);
|
||||
auto limits = getPieceUpperLimits(pieces, moveDirection);
|
||||
// for each break, move all the pieces above the break down by the removed amount
|
||||
auto limits = getPieceLimits(pieces, moveDirection);
|
||||
// for each break, move all the pieces below the break up by the removed amount
|
||||
// for the break
|
||||
for (auto& breakItem : sortedBreaks) {
|
||||
// check each break against all the pieces
|
||||
Base::Vector3d netBreakDisplace = moveDirection * (removedLengthFromObj(*breakItem.breakObj) - Gap.getValue());
|
||||
Base::Vector3d netBreakDisplace =
|
||||
moveDirection * (removedLengthFromObj(*breakItem.breakObj) - Gap.getValue());
|
||||
size_t iPiece{0};
|
||||
for (auto& pieceHighLimit : limits) {
|
||||
for (auto& pieceLimit : limits) {
|
||||
// check each piece against the current break using a fuzzy equality
|
||||
if (pieceHighLimit < breakItem.lowLimit ||
|
||||
DU::fpCompare(pieceHighLimit, breakItem.lowLimit, Precision::Confusion()) ) {
|
||||
if (moveThisPiece(pieceLimit, breakItem, moveDirection)) {
|
||||
// piece is below the break, move it up
|
||||
TopoDS_Shape temp = ShapeUtils::moveShape(pieces.at(iPiece), netBreakDisplace);
|
||||
pieces.at(iPiece) = temp;
|
||||
@@ -330,6 +330,10 @@ TopoDS_Shape DrawBrokenView::compressVertical(const TopoDS_Shape& shapeToCompre
|
||||
//! plane normal) and a point inside the half space (pointInSpace).
|
||||
TopoDS_Shape DrawBrokenView::makeHalfSpace(Base::Vector3d planePoint, Base::Vector3d planeNormal, Base::Vector3d pointInSpace) const
|
||||
{
|
||||
// Base::Console().Message("DBV::makeHalfSpace - planePoint: %s normal: %s pointInSpace: %s\n",
|
||||
// DU::formatVector(planePoint).c_str(),
|
||||
// DU::formatVector(planeNormal).c_str(),
|
||||
// DU::formatVector(pointInSpace).c_str());
|
||||
gp_Pnt origin = DU::togp_Pnt(planePoint);
|
||||
gp_Dir axis = DU::togp_Dir(planeNormal);
|
||||
gp_Pln plane(origin, axis);
|
||||
@@ -361,13 +365,21 @@ std::pair<Base::Vector3d, Base::Vector3d> DrawBrokenView::breakPointsFromObj(con
|
||||
//! perpendicular to the break lines.
|
||||
Base::Vector3d DrawBrokenView::directionFromObj(const App::DocumentObject& breakObj) const
|
||||
{
|
||||
// Base::Console().Message("DBV::directionFromObj()\n");
|
||||
std::pair<Base::Vector3d, Base::Vector3d> ends = breakPointsFromObj(breakObj);
|
||||
Base::Vector3d direction = ends.second - ends.first;
|
||||
direction.Normalize();
|
||||
// does this need to be oriented?
|
||||
return DU::closestBasis(direction);
|
||||
}
|
||||
|
||||
|
||||
//! extract the breakDirection from the break object in a form the gui will understand.
|
||||
Base::Vector3d DrawBrokenView::guiDirectionFromObj(const App::DocumentObject& breakObj) const
|
||||
{
|
||||
return projectPoint(directionFromObj(breakObj));
|
||||
}
|
||||
|
||||
//! calculate the length to be removed as specified by break object.
|
||||
double DrawBrokenView::removedLengthFromObj(const App::DocumentObject& breakObj) const
|
||||
{
|
||||
@@ -445,12 +457,28 @@ std::pair<Base::Vector3d, Base::Vector3d> DrawBrokenView::breakPointsFromSketch(
|
||||
TopoDS_Edge last = sketchEdges.back();
|
||||
if ((isVertical(first) && isVertical(last)) ||
|
||||
(isHorizontal(first) && isHorizontal(last))) {
|
||||
// get points on each edge that are in line with the center of the sketch
|
||||
// along the break direction
|
||||
Bnd_Box edgeBox;
|
||||
edgeBox.SetGap(0.0);
|
||||
BRepBndLib::AddOptimal(first, edgeBox);
|
||||
BRepBndLib::AddOptimal(last, edgeBox);
|
||||
double xMin = 0, xMax = 0, yMin = 0, yMax = 0, zMin = 0, zMax = 0;
|
||||
edgeBox.Get(xMin, yMin, zMin, xMax, yMax, zMax);
|
||||
Base::Vector3d center( (xMin + xMax) / 2.0,
|
||||
(yMin + yMax) / 2.0,
|
||||
(zMin + zMax) / 2.0 );
|
||||
auto ends0 = SU::getEdgeEnds(first);
|
||||
// trouble here if the break points are wildly out of line?
|
||||
// std::pair makeCardinal(p0, p1) to force horiz or vert?
|
||||
auto break0 = (ends0.first + ends0.second) / 2.0;
|
||||
auto dir0 = ends0.second - ends0.first;
|
||||
dir0.Normalize();
|
||||
// get a point on edge first on a perpendicular line through center
|
||||
auto break0 = center.Perpendicular(ends0.first, dir0);
|
||||
|
||||
auto ends1 = SU::getEdgeEnds(last);
|
||||
auto break1 = (ends1.first + ends1.second) / 2.0;
|
||||
auto dir1 = ends1.second - ends1.first;
|
||||
dir1.Normalize();
|
||||
auto break1 = center.Perpendicular(ends1.first, dir1);
|
||||
|
||||
return { break0, break1 };
|
||||
}
|
||||
|
||||
@@ -496,31 +524,29 @@ std::pair<Base::Vector3d, Base::Vector3d> DrawBrokenView::breakBoundsFromObj(con
|
||||
//! broken view. used in making break lines.
|
||||
std::pair<Base::Vector3d, Base::Vector3d> DrawBrokenView::breakBoundsFromSketch(const App::DocumentObject& breakObj) const
|
||||
{
|
||||
// Base::Console().Message("DBV::breakBoundsFromSketch()\n");
|
||||
//Base::Console().Message("DBV::breakBoundsFromSketch()\n");
|
||||
std::pair<Base::Vector3d, Base::Vector3d> breakPoints = breakPointsFromObj(breakObj);
|
||||
Base::Vector3d anchor = (breakPoints.first + breakPoints.second) / 2.0;
|
||||
Base::Vector3d center = (breakPoints.first + breakPoints.second) / 2.0;
|
||||
Base::Vector3d breakDir = directionFromObj(breakObj);
|
||||
breakDir.Normalize();
|
||||
Base::Vector3d lineDir = makePerpendicular(breakDir);
|
||||
lineDir.Normalize();
|
||||
|
||||
// is this right? or do we need to project the points first? Should be alright
|
||||
// if the break points are not skewed?
|
||||
double removed = (breakPoints.first - breakPoints.second).Length();
|
||||
Base::Vector3d perpDir = makePerpendicular(breakDir);
|
||||
perpDir.Normalize();
|
||||
|
||||
// get the midpoint of the zigzags
|
||||
Base::Vector3d ptOnLine0 = anchor + breakDir * removed / 2.0;
|
||||
Base::Vector3d ptOnLine1 = anchor - breakDir * removed / 2.0;
|
||||
Base::Vector3d ptOnLine0 = center + breakDir * removedLengthFromObj(breakObj) / 2.0;
|
||||
Base::Vector3d ptOnLine1 = center - breakDir * removedLengthFromObj(breakObj) / 2.0;
|
||||
double lineLength = breaklineLength(breakObj);
|
||||
|
||||
Base::Vector3d corner0 = ptOnLine0 - lineDir * lineLength / 2.0;
|
||||
Base::Vector3d corner1 = ptOnLine1 + lineDir * lineLength / 2.0;
|
||||
Base::Vector3d corner0 = ptOnLine0 - perpDir * lineLength / 2.0;
|
||||
Base::Vector3d corner1 = ptOnLine1 + perpDir * lineLength / 2.0;
|
||||
corner0 = mapPoint3dToView(corner0);
|
||||
corner1 = mapPoint3dToView(corner1);
|
||||
|
||||
// these are unscaled, unrotated points
|
||||
return{corner0, corner1};
|
||||
}
|
||||
|
||||
|
||||
//! extract the boundary of the break lines from an edge
|
||||
std::pair<Base::Vector3d, Base::Vector3d> DrawBrokenView::breakBoundsFromEdge(const App::DocumentObject& breakObj) const
|
||||
{
|
||||
@@ -732,15 +758,15 @@ std::vector<TopoDS_Edge> DrawBrokenView::edgesFromCompound(TopoDS_Shape compound
|
||||
}
|
||||
|
||||
|
||||
//! find the upper limits of each piece's bounding box in direction (if we support oblique projection directions, then the
|
||||
//! find the limits of each piece's bounding box in cardinal direction (if we support oblique projection directions, then the
|
||||
//! piece will have to be transformed to align with OXYZ cardinal axes as in DrawViewPart::getSizeAlongVector)
|
||||
std::vector<double> DrawBrokenView::getPieceUpperLimits(const std::vector<TopoDS_Shape>& pieces, Base::Vector3d direction)
|
||||
PieceLimitList DrawBrokenView::getPieceLimits(const std::vector<TopoDS_Shape>& pieces, Base::Vector3d direction)
|
||||
{
|
||||
// Base::Console().Message("DBV::getPieceUpperLimits(%s)\n", DU::formatVector(direction).c_str());
|
||||
Base::Vector3d stdX{1.0, 0.0, 0.0};
|
||||
Base::Vector3d stdY{0.0, 1.0, 0.0};
|
||||
Base::Vector3d stdZ{0.0, 0.0, 1.0};
|
||||
std::vector<double> limits;
|
||||
PieceLimitList limits;
|
||||
limits.reserve(pieces.size());
|
||||
for (auto& item : pieces) {
|
||||
Bnd_Box pieceBox;
|
||||
@@ -748,13 +774,21 @@ std::vector<double> DrawBrokenView::getPieceUpperLimits(const std::vector<TopoDS
|
||||
BRepBndLib::AddOptimal(item, pieceBox);
|
||||
double xMin = 0, xMax = 0, yMin = 0, yMax = 0, zMin = 0, zMax = 0;
|
||||
pieceBox.Get(xMin, yMin, zMin, xMax, yMax, zMax);
|
||||
// this is a bit crude. will only work in well behaved cases
|
||||
if (DU::fpCompare(std::fabs(direction.Dot(stdX)), 1.0, EWTOLERANCE)) {
|
||||
limits.push_back(xMax);
|
||||
PieceLimitEntry newEntry;
|
||||
newEntry.highLimit = xMax;
|
||||
newEntry.lowLimit = xMin;
|
||||
limits.push_back(newEntry);
|
||||
} else if (DU::fpCompare(std::fabs(direction.Dot(stdY)), 1.0, EWTOLERANCE)) {
|
||||
limits.push_back(yMax);
|
||||
PieceLimitEntry newEntry;
|
||||
newEntry.highLimit = yMax;
|
||||
newEntry.lowLimit = yMin;
|
||||
limits.push_back(newEntry);
|
||||
} else {
|
||||
limits.push_back(zMax);
|
||||
PieceLimitEntry newEntry;
|
||||
newEntry.highLimit = zMax;
|
||||
newEntry.lowLimit = zMin;
|
||||
limits.push_back(newEntry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -779,6 +813,8 @@ std::vector<TopoDS_Shape> DrawBrokenView::getPieces(TopoDS_Shape brokenShape)
|
||||
//! sort the breaks that match direction by their minimum limit
|
||||
BreakList DrawBrokenView::makeSortedBreakList(const std::vector<App::DocumentObject*>& breaks, Base::Vector3d direction, bool descend) const
|
||||
{
|
||||
// Base::Console().Message("DBV::makeSortedBreakList(%d, %s)\n", breaks.size(),
|
||||
// DU::formatVector(direction).c_str());
|
||||
Base::Vector3d stdX{1.0, 0.0, 0.0};
|
||||
Base::Vector3d stdY{0.0, 1.0, 0.0};
|
||||
Base::Vector3d stdZ{0.0, 0.0, 1.0};
|
||||
@@ -786,7 +822,7 @@ BreakList DrawBrokenView::makeSortedBreakList(const std::vector<App::DocumentObj
|
||||
BreakList unsorted;
|
||||
for (auto& breakObj : breaks) {
|
||||
auto breakDirection = directionFromObj(*breakObj);
|
||||
if (breakDirection.IsEqual(direction, EWTOLERANCE)) {
|
||||
if (DU::fpCompare(std::fabs(direction.Dot(breakDirection)), 1.0, EWTOLERANCE)) {
|
||||
// this break interests us
|
||||
BreakListEntry newEntry;
|
||||
newEntry.breakObj = breakObj;
|
||||
@@ -856,41 +892,37 @@ BreakList DrawBrokenView::sortBreaks(BreakList& inList, bool descend)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//! transform a 3d point into its position within the broken view. used in creating
|
||||
//! dimensions.
|
||||
Base::Vector3d DrawBrokenView::mapPoint3dToView(Base::Vector3d point3d) const
|
||||
{
|
||||
// Base::Console().Message("DBV::mapPoint3dToView(%s)\n", DU::formatVector(point3d).c_str());
|
||||
Base::Vector3d stdX(1.0, 0.0, 0.0);
|
||||
Base::Vector3d stdY(0.0, 1.0, 0.0);
|
||||
//Base::Console().Message("DBV::mapPoint3dToView(%s)\n", DU::formatVector(point3d).c_str());
|
||||
Base::Vector3d result{point3d};
|
||||
|
||||
// if the input point has been projected, then we have to use stdX and stdY instead
|
||||
// of XDirection and YDirection.
|
||||
Base::Vector3d point2d = projectPoint(point3d, false); // don't invert
|
||||
|
||||
auto breaksAll = Breaks.getValues();
|
||||
bool descend = false;
|
||||
auto moveXDirection = DU::closestBasis(DU::toVector3d(getProjectionCS().XDirection()));
|
||||
auto moveXDirection = DU::closestBasisOriented(DU::toVector3d(getProjectionCS().XDirection()));
|
||||
|
||||
// get the breaks that move us in X
|
||||
auto sortedXBreaks = makeSortedBreakList(breaksAll, moveXDirection, descend);
|
||||
double xLimit = point2d.x;
|
||||
double xShift = shiftAmountShrink(xLimit, sortedXBreaks);
|
||||
Base::Vector3d xMove = stdX * xShift; // move to the right (+X)
|
||||
double xLimit = DU::coordinateForDirection(point3d, moveXDirection);
|
||||
|
||||
auto moveYDirection = DU::closestBasis(DU::toVector3d(getProjectionCS().YDirection()));
|
||||
double xShift = shiftAmountShrink(xLimit, moveXDirection, sortedXBreaks);
|
||||
Base::Vector3d xMove = moveXDirection * xShift; // move to the right (+X)
|
||||
|
||||
auto moveYDirection = DU::closestBasisOriented(DU::toVector3d(getProjectionCS().YDirection()));
|
||||
descend = false;
|
||||
// get the breaks that move us in Y
|
||||
auto sortedYBreaks = makeSortedBreakList(breaksAll, moveYDirection, descend);
|
||||
double yLimit = point2d.y;
|
||||
double yShift = shiftAmountShrink(yLimit, sortedYBreaks);
|
||||
Base::Vector3d yMove = stdY * yShift; // move up (+Y)
|
||||
double yLimit = DU::coordinateForDirection(point3d, moveYDirection);
|
||||
double yShift = shiftAmountShrink(yLimit, moveYDirection, sortedYBreaks);
|
||||
Base::Vector3d yMove = moveYDirection * yShift; // move up (+Y)
|
||||
|
||||
point2d = point2d + xMove + yMove;
|
||||
auto moved3d = point3d + xMove + yMove;
|
||||
result = moved3d - getCompressedCentroid();
|
||||
|
||||
Base::Vector3d compressedCoM = projectPoint(getCompressedCentroid(), false);
|
||||
result = point2d - compressedCoM;
|
||||
result = projectPoint(result, false);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -905,56 +937,105 @@ Base::Vector3d DrawBrokenView::mapPoint2dFromView(Base::Vector3d point2d) const
|
||||
|
||||
// convert point2d in view to pseudo-3d view coords
|
||||
Base::Vector3d projectedCoM = projectPoint(getCompressedCentroid(), false);
|
||||
Base::Vector3d result = projectedCoM + point2d;
|
||||
|
||||
// now shift down and left
|
||||
auto breaksAll = Breaks.getValues();
|
||||
|
||||
Base::Vector3d pseudo3dx = point2d + projectedCoM;
|
||||
double xReverser = -1.0;
|
||||
auto moveXDirection = DU::closestBasisOriented(DU::toVector3d(getProjectionCS().XDirection()));
|
||||
if (isDirectionReversed(moveXDirection)) {
|
||||
pseudo3dx = point2d - projectedCoM;
|
||||
xReverser = 1.0;
|
||||
}
|
||||
bool descend = false; // should be false so we move from lowest break to highest?
|
||||
auto moveXDirection = DU::closestBasis(DU::toVector3d(getProjectionCS().XDirection()));
|
||||
// get the breaks that moved us in X
|
||||
auto sortedXBreaks = makeSortedBreakListCompressed(breaksAll, moveXDirection, descend);
|
||||
double xLimit = result.x;
|
||||
double xShift = shiftAmountExpand(xLimit, sortedXBreaks);
|
||||
Base::Vector3d xMove = stdX * xShift * -1.0; // move to the left (-X)
|
||||
double xLimit = pseudo3dx.x;
|
||||
double xShift = shiftAmountExpand(xLimit, moveXDirection, sortedXBreaks);
|
||||
Base::Vector3d xMoved = pseudo3dx + stdX * xShift * xReverser; // move to the left (-X)
|
||||
|
||||
auto moveYDirection = DU::closestBasis(DU::toVector3d(getProjectionCS().YDirection()));
|
||||
Base::Vector3d pseudo3dy = point2d + projectedCoM;
|
||||
double yReverser = -1.0;
|
||||
auto moveYDirection = DU::closestBasisOriented(DU::toVector3d(getProjectionCS().YDirection()));
|
||||
if (isDirectionReversed(moveXDirection)) {
|
||||
pseudo3dy = point2d - projectedCoM;
|
||||
yReverser = 1.0;
|
||||
}
|
||||
descend = false;
|
||||
// get the breaks that moved us in Y
|
||||
auto sortedYBreaks = makeSortedBreakListCompressed(breaksAll, moveYDirection, descend);
|
||||
double yLimit = result.y;
|
||||
double yShift = shiftAmountExpand(yLimit, sortedYBreaks);
|
||||
Base::Vector3d yMove = stdY * yShift * -1.0; // move down (-Y)
|
||||
double yLimit = pseudo3dy.y;
|
||||
double yShift = shiftAmountExpand(yLimit, moveYDirection, sortedYBreaks);
|
||||
Base::Vector3d yMoved = pseudo3dy + stdY * yShift * yReverser; // move down (-Y)
|
||||
|
||||
result = result + xMove + yMove;
|
||||
return result;
|
||||
Base::Vector3d movedResult{xMoved.x, yMoved.y, 0.0};
|
||||
return movedResult;
|
||||
}
|
||||
|
||||
|
||||
//! returns the amount a coordinate needs to move to reflect the effect of the breaks to the right/above
|
||||
//! the input. used in mapping points to the broken view.
|
||||
double DrawBrokenView::shiftAmountShrink(double pointCoord, const BreakList& sortedBreaks) const
|
||||
double DrawBrokenView::shiftAmountShrink(double pointCoord, Base::Vector3d direction, const BreakList& sortedBreaks) const
|
||||
{
|
||||
// Base::Console().Message("DBV::shiftAmountShrink(%.3f, %d)\n", pointCoord, sortedBreaks.size());
|
||||
// Base::Console().Message("DBV::shiftAmountShrink(%.3f, %s, %d)\n", pointCoord,
|
||||
// DU::formatVector(direction).c_str(), sortedBreaks.size());
|
||||
double shift{0};
|
||||
for (auto& breakItem : sortedBreaks) {
|
||||
if (pointCoord >= breakItem.highLimit) {
|
||||
// leave alone, this break doesn't affect us
|
||||
continue;
|
||||
}
|
||||
if (isDirectionReversed(direction)) {
|
||||
if (pointCoord <= breakItem.lowLimit) {
|
||||
// h--------l -ve
|
||||
// p
|
||||
// point is right/above break
|
||||
// leave alone, this break doesn't affect us
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pointCoord < breakItem.lowLimit ||
|
||||
DU::fpCompare(pointCoord, breakItem.lowLimit, Precision::Confusion()) ) {
|
||||
// move right/up by the removed area less the gap
|
||||
shift += removedLengthFromObj(*breakItem.breakObj) - Gap.getValue();
|
||||
continue;
|
||||
}
|
||||
if (pointCoord > breakItem.highLimit ||
|
||||
DU::fpCompare(pointCoord, breakItem.highLimit, Precision::Confusion()) ) {
|
||||
// h--------l -ve
|
||||
// p
|
||||
// point is left/below break, but we
|
||||
shift += removedLengthFromObj(*breakItem.breakObj) - Gap.getValue();
|
||||
continue;
|
||||
}
|
||||
|
||||
// break.start < value < break.end - point is in the break area
|
||||
// we move our point by a fraction of the Gap length
|
||||
double penetration = pointCoord - breakItem.lowLimit;
|
||||
double removed = removedLengthFromObj(*breakItem.breakObj);
|
||||
double factor = 1 - (penetration / removed);
|
||||
double netRemoved = breakItem.highLimit - factor * Gap.getValue();
|
||||
shift += netRemoved - pointCoord;
|
||||
// h--------l -ve
|
||||
// p
|
||||
// break.start < value < break.end - point is in the break area
|
||||
// we move our point by a fraction of the Gap length
|
||||
double penetration = fabs(pointCoord - breakItem.highLimit); // start(high) to point
|
||||
double removed = removedLengthFromObj(*breakItem.breakObj);
|
||||
double factor = 1 - (penetration / removed);
|
||||
double netRemoved = breakItem.lowLimit - factor * Gap.getValue();
|
||||
shift += fabs(netRemoved - pointCoord);
|
||||
|
||||
|
||||
} else {
|
||||
if (pointCoord >= breakItem.highLimit) {
|
||||
// l--------h +ve
|
||||
// p
|
||||
// leave alone, this break doesn't affect us
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pointCoord < breakItem.lowLimit ||
|
||||
DU::fpCompare(pointCoord, breakItem.lowLimit, Precision::Confusion()) ) {
|
||||
// l--------h +ve
|
||||
// p
|
||||
// move right/up by the removed area less the gap
|
||||
shift += removedLengthFromObj(*breakItem.breakObj) - Gap.getValue();
|
||||
continue;
|
||||
}
|
||||
|
||||
// l--------h +ve
|
||||
// p
|
||||
// break.start < value < break.end - point is in the break area
|
||||
// we move our point by a fraction of the Gap length
|
||||
double penetration = fabs(pointCoord - breakItem.lowLimit);
|
||||
double removed = removedLengthFromObj(*breakItem.breakObj);
|
||||
double factor = 1 - (penetration / removed);
|
||||
double netRemoved = breakItem.highLimit - factor * Gap.getValue();
|
||||
shift += fabs(netRemoved - pointCoord);
|
||||
}
|
||||
}
|
||||
|
||||
return shift;
|
||||
@@ -963,30 +1044,67 @@ double DrawBrokenView::shiftAmountShrink(double pointCoord, const BreakList& sor
|
||||
|
||||
//! returns the amount a compressed coordinate needs to be shifted to reverse the effect of breaking
|
||||
//! the source shapes
|
||||
double DrawBrokenView::shiftAmountExpand(double pointCoord, const BreakList& sortedBreaks) const
|
||||
double DrawBrokenView::shiftAmountExpand(double pointCoord, Base::Vector3d direction, const BreakList& sortedBreaks) const
|
||||
{
|
||||
// Base::Console().Message("DBV::shiftAmountExpand(%.3f, %d)\n", pointCoord, sortedBreaks.size());
|
||||
// Base::Console().Message("DBV::shiftAmountExpand(%.3f, %s, %d)\n", pointCoord,
|
||||
// DU::formatVector(direction).c_str(), sortedBreaks.size());
|
||||
double shift{0};
|
||||
for (auto& breakItem : sortedBreaks) {
|
||||
if (pointCoord >= breakItem.highLimit) {
|
||||
// leave alone, this break doesn't affect us
|
||||
continue;
|
||||
}
|
||||
if (isDirectionReversed(direction)) {
|
||||
if (pointCoord <= breakItem.lowLimit) {
|
||||
// h--------l -ve
|
||||
// p
|
||||
// leave alone, this break doesn't affect us
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pointCoord < breakItem.lowLimit ||
|
||||
DU::fpCompare(pointCoord, breakItem.lowLimit, Precision::Confusion()) ) {
|
||||
// move by the whole removed area
|
||||
shift += breakItem.netRemoved;
|
||||
continue;
|
||||
}
|
||||
if (pointCoord > breakItem.highLimit ||
|
||||
DU::fpCompare(pointCoord, breakItem.highLimit, Precision::Confusion()) ) {
|
||||
// h--------l -ve
|
||||
// p
|
||||
|
||||
// break.start < value < break.end - point is in the break area
|
||||
// we move our point by the break's net removed * the penetration factor
|
||||
double gapPenetration = pointCoord - breakItem.lowLimit;
|
||||
double removed = removedLengthFromObj(*breakItem.breakObj);
|
||||
double factor = 1 - gapPenetration / Gap.getValue();
|
||||
double shiftAmount = factor * (removed - Gap.getValue());
|
||||
shift += shiftAmount;
|
||||
// move by the whole removed area
|
||||
shift += breakItem.netRemoved;
|
||||
continue;
|
||||
}
|
||||
|
||||
// h--------l -ve
|
||||
// p
|
||||
// break.start < value < break.end - point is in the break area
|
||||
// we move our point by the break's net removed * the penetration factor
|
||||
double gapPenetration = fabs(pointCoord - breakItem.lowLimit);
|
||||
double removed = removedLengthFromObj(*breakItem.breakObj);
|
||||
double factor = gapPenetration / Gap.getValue();
|
||||
double shiftAmount = factor * (removed - Gap.getValue());
|
||||
shift += shiftAmount;
|
||||
|
||||
} else {
|
||||
if (pointCoord >= breakItem.highLimit) {
|
||||
// l--------h +ve
|
||||
// p
|
||||
// leave alone, this break doesn't affect us
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pointCoord < breakItem.lowLimit ||
|
||||
DU::fpCompare(pointCoord, breakItem.lowLimit, Precision::Confusion()) ) {
|
||||
// l--------h +ve
|
||||
// p
|
||||
// move by the whole removed area
|
||||
shift += breakItem.netRemoved;
|
||||
continue;
|
||||
}
|
||||
|
||||
// l--------h +ve
|
||||
// p
|
||||
// break.start < value < break.end - point is in the break area
|
||||
// we move our point by the break's net removed * the penetration factor
|
||||
double gapPenetration = pointCoord - breakItem.lowLimit;
|
||||
double removed = removedLengthFromObj(*breakItem.breakObj);
|
||||
double factor = 1 - gapPenetration / Gap.getValue();
|
||||
double shiftAmount = factor * (removed - Gap.getValue());
|
||||
shift += shiftAmount;
|
||||
}
|
||||
}
|
||||
|
||||
return shift;
|
||||
@@ -1014,6 +1132,46 @@ Base::Vector3d DrawBrokenView::makePerpendicular(Base::Vector3d inDir) const
|
||||
return DU::toVector3d(gRotated);
|
||||
}
|
||||
|
||||
//! true if this piece should be moved
|
||||
bool DrawBrokenView::moveThisPiece(PieceLimitEntry piece,
|
||||
BreakListEntry breakItem,
|
||||
Base::Vector3d moveDirection) const
|
||||
{
|
||||
if (isDirectionReversed(moveDirection)) {
|
||||
// -ve direction
|
||||
if (piece.lowLimit > breakItem.highLimit ||
|
||||
DU::fpCompare(piece.lowLimit, breakItem.highLimit, Precision::Confusion()) ) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// +ve direction
|
||||
if (piece.highLimit < breakItem.lowLimit ||
|
||||
DU::fpCompare(piece.highLimit, breakItem.lowLimit, Precision::Confusion()) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//! true if direction is the reversed of a cardinal direction. (1.0, 0.0, 0.0)
|
||||
//! returns false, (-1.0, 0.0, 0.0) returns true;
|
||||
bool DrawBrokenView::isDirectionReversed(Base::Vector3d direction) const
|
||||
{
|
||||
Base::Vector3d stdX{1.0, 0.0, 0.0};
|
||||
Base::Vector3d stdY{0.0, 1.0, 0.0};
|
||||
Base::Vector3d stdZ{0.0, 0.0, 1.0};
|
||||
if (DU::fpCompare(std::fabs(direction.Dot(stdX)), 1.0, EWTOLERANCE)) {
|
||||
return DU::fpCompare(direction.Dot(stdX), -1.0, EWTOLERANCE);
|
||||
}
|
||||
if (DU::fpCompare(std::fabs(direction.Dot(stdY)), 1.0, EWTOLERANCE)) {
|
||||
return DU::fpCompare(direction.Dot(stdY), -1.0, EWTOLERANCE);
|
||||
}
|
||||
if (DU::fpCompare(std::fabs(direction.Dot(stdZ)), 1.0, EWTOLERANCE)) {
|
||||
return DU::fpCompare(direction.Dot(stdZ), -1.0, EWTOLERANCE);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void DrawBrokenView::printBreakList(const std::string& text, const BreakList& inBreaks) const
|
||||
{
|
||||
Base::Console().Message("DBV - %s\n", text.c_str());
|
||||
|
||||
@@ -47,9 +47,14 @@ struct BreakListEntry {
|
||||
// TODO: can the gap size change during the lifetime of BreakListEntry? if
|
||||
// so, we need to save the gap size @ creation time?
|
||||
};
|
||||
|
||||
using BreakList = std::vector<BreakListEntry>;
|
||||
|
||||
struct PieceLimitEntry {
|
||||
double lowLimit;
|
||||
double highLimit;
|
||||
};
|
||||
using PieceLimitList = std::vector<PieceLimitEntry>;
|
||||
|
||||
class TechDrawExport DrawBrokenView: public TechDraw::DrawViewPart
|
||||
{
|
||||
PROPERTY_HEADER_WITH_OVERRIDE(TechDraw::DrawBrokenView);
|
||||
@@ -75,6 +80,7 @@ public:
|
||||
std::pair<Base::Vector3d, Base::Vector3d>
|
||||
breakBoundsFromObj(const App::DocumentObject& breakObj) const;
|
||||
Base::Vector3d directionFromObj(const App::DocumentObject& breakObj) const;
|
||||
Base::Vector3d guiDirectionFromObj(const App::DocumentObject& breakObj) const;
|
||||
|
||||
static bool isBreakObject(const App::DocumentObject& breakObj);
|
||||
static bool isBreakObjectSketch(const App::DocumentObject& breakObj);
|
||||
@@ -114,7 +120,7 @@ private:
|
||||
TopoDS_Shape compressHorizontal(const TopoDS_Shape& inShape) const;
|
||||
TopoDS_Shape compressVertical(const TopoDS_Shape& inShape) const;
|
||||
|
||||
static std::vector<double> getPieceUpperLimits(const std::vector<TopoDS_Shape>& pieces, Base::Vector3d direction);
|
||||
static PieceLimitList getPieceLimits(const std::vector<TopoDS_Shape>& pieces, Base::Vector3d direction);
|
||||
|
||||
BreakList makeSortedBreakList(const std::vector<App::DocumentObject*>& breaks, Base::Vector3d direction, bool descend = false) const;
|
||||
BreakList makeSortedBreakListCompressed(const std::vector<App::DocumentObject*>& breaks, Base::Vector3d moveDirection, bool descend = false) const;
|
||||
@@ -123,8 +129,8 @@ private:
|
||||
static bool breakLess(const BreakListEntry& entry0, const BreakListEntry& entry1);
|
||||
|
||||
// double pointToLimit(const Base::Vector3d& inPoint, const Base::Vector3d& direction) const;
|
||||
double shiftAmountShrink(double pointCoord, const BreakList& sortedBreaks) const;
|
||||
double shiftAmountExpand(double pointCoord, const BreakList& sortedBreaks) const;
|
||||
double shiftAmountShrink(double pointCoord, Base::Vector3d direction, const BreakList& sortedBreaks) const;
|
||||
double shiftAmountExpand(double pointCoord, Base::Vector3d direction, const BreakList& sortedBreaks) const;
|
||||
|
||||
void printBreakList(const std::string& text, const BreakList& inBreaks) const;
|
||||
|
||||
@@ -132,6 +138,9 @@ private:
|
||||
scalePair(std::pair<Base::Vector3d, Base::Vector3d> inPair) const;
|
||||
Base::Vector3d makePerpendicular(Base::Vector3d inDir) const;
|
||||
|
||||
bool moveThisPiece(PieceLimitEntry piece, BreakListEntry breakItem, Base::Vector3d moveDirection) const;
|
||||
bool isDirectionReversed(Base::Vector3d direction) const;
|
||||
|
||||
Base::Vector3d m_unbrokenCenter;
|
||||
TopoDS_Shape m_compressedShape;
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ std::string DrawUtil::getGeomTypeFromName(const std::string& geomName)
|
||||
std::stringstream ErrorMsg;
|
||||
|
||||
if (boost::regex_search(begin, end, what, re, flags)) {
|
||||
return what.str();//TODO: use std::stoi() in c++11
|
||||
return what.str();
|
||||
} else {
|
||||
ErrorMsg << "In getGeomTypeFromName: malformed geometry name - " << geomName;
|
||||
throw Base::ValueError(ErrorMsg.str());
|
||||
@@ -861,18 +861,15 @@ double DrawUtil::getWidthInDirection(gp_Dir direction, TopoDS_Shape& shape)
|
||||
//! cardinal direction or the reverse of a cardinal direction.
|
||||
gp_Vec DrawUtil::maskDirection(gp_Vec inVec, gp_Dir directionToMask)
|
||||
{
|
||||
if (directionToMask.XYZ().IsEqual(gp::OX().Direction().XYZ(), EWTOLERANCE) ||
|
||||
directionToMask.XYZ().IsEqual(gp::OX().Direction().Reversed().XYZ(), EWTOLERANCE)) {
|
||||
return {0.0, inVec.Y(), inVec.Z()};
|
||||
if (fpCompare(std::fabs(directionToMask.Dot(gp::OX().Direction().XYZ())), 1.0, EWTOLERANCE)) {
|
||||
return {0.0, inVec.Y(), inVec.Z()};
|
||||
}
|
||||
|
||||
if (directionToMask.XYZ().IsEqual(gp::OY().Direction().XYZ(), EWTOLERANCE) ||
|
||||
directionToMask.XYZ().IsEqual(gp::OY().Direction().Reversed().XYZ(), EWTOLERANCE)) {
|
||||
if (fpCompare(std::fabs(directionToMask.Dot(gp::OY().Direction().XYZ())), 1.0, EWTOLERANCE)) {
|
||||
return {inVec.X(), 0.0, inVec.Z()};
|
||||
}
|
||||
|
||||
if (directionToMask.XYZ().IsEqual(gp::OZ().Direction().XYZ(), EWTOLERANCE) ||
|
||||
directionToMask.XYZ().IsEqual(gp::OZ().Direction().Reversed().XYZ(), EWTOLERANCE)) {
|
||||
if (fpCompare(std::fabs(directionToMask.Dot(gp::OZ().Direction().XYZ())), 1.0, EWTOLERANCE)) {
|
||||
return {inVec.X(), inVec.Y(), 0.0};
|
||||
}
|
||||
|
||||
@@ -880,6 +877,19 @@ gp_Vec DrawUtil::maskDirection(gp_Vec inVec, gp_Dir directionToMask)
|
||||
return {};
|
||||
}
|
||||
|
||||
Base::Vector3d DrawUtil::maskDirection(Base::Vector3d inVec, Base::Vector3d directionToMask)
|
||||
{
|
||||
return toVector3d(maskDirection(togp_Vec(inVec), togp_Vec(directionToMask)));
|
||||
}
|
||||
|
||||
//! get the coordinate of inPoint for the cardinal unit direction.
|
||||
double DrawUtil::coordinateForDirection(Base::Vector3d inPoint, Base::Vector3d cardinal)
|
||||
{
|
||||
auto masked = maskDirection(inPoint, cardinal);
|
||||
auto stripped = inPoint - masked;
|
||||
return stripped.x + stripped.y + stripped.z;
|
||||
}
|
||||
|
||||
//based on Function provided by Joe Dowsett, 2014
|
||||
double DrawUtil::sensibleScale(double working_scale)
|
||||
{
|
||||
|
||||
@@ -154,6 +154,8 @@ public:
|
||||
|
||||
static double getWidthInDirection(gp_Dir direction, TopoDS_Shape& shape);
|
||||
static gp_Vec maskDirection(gp_Vec inVec, gp_Dir directionToMask);
|
||||
static Base::Vector3d maskDirection(Base::Vector3d inVec, Base::Vector3d directionToMask);
|
||||
static double coordinateForDirection(Base::Vector3d inPoint, Base::Vector3d cardinal);
|
||||
|
||||
static double getDefaultLineWeight(std::string s);
|
||||
//! is pt between end1 and end2?
|
||||
|
||||
@@ -75,7 +75,7 @@ void QGIBreakLine::draw()
|
||||
Base::Vector3d horizontal{1.0, 0.0, 0.0};
|
||||
prepareGeometryChange();
|
||||
double offset = zigzagWidth / 2.0;
|
||||
if (m_direction.IsEqual(horizontal, EWTOLERANCE)) {
|
||||
if (DU::fpCompare(fabs(m_direction.Dot(horizontal)), 1.0, EWTOLERANCE)) {
|
||||
// m_direction connects the two cut points. The zigzags have
|
||||
// to be perpendicular to m_direction
|
||||
// 2x vertical zigzag
|
||||
|
||||
@@ -43,21 +43,23 @@
|
||||
#include <QByteArrayMatcher>
|
||||
#include "QGCustomRect.h"
|
||||
#include "QGCustomSvg.h"
|
||||
#include "QGCustomImage.h"
|
||||
#include "QGICMark.h"
|
||||
#include "QGIPrimPath.h"
|
||||
#include "QGSPage.h"
|
||||
#include "Rez.h"
|
||||
#include "ZVALUE.h"
|
||||
|
||||
using namespace TechDrawGui;
|
||||
using namespace TechDraw;
|
||||
|
||||
using DU = DrawUtil;
|
||||
|
||||
QGIFace::QGIFace(int index) :
|
||||
projIndex(index),
|
||||
m_hideSvgTiles(false),
|
||||
projIndex(index),
|
||||
m_hatchRotation(0.0)
|
||||
{
|
||||
m_segCount = 0;
|
||||
// setFillMode(NoFill);
|
||||
isHatched(false);
|
||||
setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);
|
||||
|
||||
@@ -68,26 +70,27 @@ QGIFace::QGIFace(int index) :
|
||||
m_pen.setStyle(m_styleCurrent);
|
||||
setLineWeight(0.0); //0 = cosmetic
|
||||
|
||||
setPrettyNormal();
|
||||
m_texture = QPixmap(); //empty texture
|
||||
|
||||
m_svgHatchArea = new QGCustomRect();
|
||||
m_svgHatchArea->setParentItem(this);
|
||||
|
||||
m_svgCol = SVGCOLDEFAULT;
|
||||
m_fillScale = 1.0;
|
||||
m_imageSvgHatchArea = new QGCustomImage();
|
||||
m_imageSvgHatchArea->setParentItem(this);
|
||||
|
||||
getParameters();
|
||||
|
||||
// set up style & colour defaults
|
||||
m_colDefFill = App::Color(static_cast<uint32_t>(Preferences::getPreferenceGroup("Colors")->GetUnsigned("FaceColor", 0xFFFFFF)))
|
||||
m_colDefFill = App::Color(static_cast<uint32_t>(Preferences::getPreferenceGroup("Colors")->GetUnsigned("FaceColor", COLWHITE)))
|
||||
.asValue<QColor>();
|
||||
m_colDefFill.setAlpha(Preferences::getPreferenceGroup("Colors")->GetBool("ClearFace", false) ? 0 : 255);
|
||||
m_colDefFill.setAlpha(Preferences::getPreferenceGroup("Colors")->GetBool("ClearFace", false) ? ALPHALOW : ALPHAHIGH);
|
||||
|
||||
m_fillDef = Qt::SolidPattern;
|
||||
m_fillSelect = Qt::SolidPattern;
|
||||
|
||||
setFillMode(m_colDefFill.alpha() ? PlainFill : NoFill);
|
||||
setFillMode(NoFill);
|
||||
if (m_colDefFill.alpha() > 0) {
|
||||
setFillMode(PlainFill);
|
||||
}
|
||||
setFill(m_colDefFill, m_fillDef);
|
||||
|
||||
m_sharedRender = new QSvgRenderer();
|
||||
@@ -106,6 +109,9 @@ void QGIFace::draw()
|
||||
// Base::Console().Message("QGIF::draw - pen style: %d\n", m_pen.style());
|
||||
setPath(m_outline); //Face boundary
|
||||
|
||||
m_svgHatchArea->hide();
|
||||
m_imageSvgHatchArea->hide();
|
||||
|
||||
if (isHatched()) {
|
||||
if (m_mode == GeomHatchFill) {
|
||||
//GeomHatch does not appear in pdf if clipping is set to true
|
||||
@@ -118,29 +124,28 @@ void QGIFace::draw()
|
||||
lineSetToFillItems(ls);
|
||||
}
|
||||
}
|
||||
m_svgHatchArea->hide();
|
||||
} else if (m_mode == SvgFill) {
|
||||
m_brush.setTexture(QPixmap());
|
||||
m_fillNormal = m_fillDef;
|
||||
m_fillStyleCurrent = m_fillNormal;
|
||||
setFlag(QGraphicsItem::ItemClipsChildrenToShape,true);
|
||||
loadSvgHatch(m_fileSpec);
|
||||
//SVG tiles need to be clipped
|
||||
setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);
|
||||
buildSvgHatch();
|
||||
m_svgHatchArea->show();
|
||||
if (exporting()) {
|
||||
buildPixHatch();
|
||||
m_imageSvgHatchArea->show();
|
||||
} else {
|
||||
buildSvgHatch();
|
||||
m_svgHatchArea->show();
|
||||
}
|
||||
} else if (m_mode == BitmapFill) {
|
||||
m_fillStyleCurrent = Qt::TexturePattern;
|
||||
m_texture = textureFromBitmap(m_fileSpec);
|
||||
m_brush.setTexture(m_texture);
|
||||
m_svgHatchArea->hide();
|
||||
} else if (m_mode == PlainFill) {
|
||||
setFill(m_colNormalFill, m_fillNormal);
|
||||
m_svgHatchArea->hide();
|
||||
}
|
||||
} else {
|
||||
// face is not hatched
|
||||
m_svgHatchArea->hide();
|
||||
}
|
||||
|
||||
show();
|
||||
}
|
||||
|
||||
@@ -174,9 +179,9 @@ void QGIFace::setPrettySel() {
|
||||
}
|
||||
|
||||
/// show or hide the edges of this face. Usually just for debugging
|
||||
void QGIFace::setDrawEdges(bool b) {
|
||||
void QGIFace::setDrawEdges(bool state) {
|
||||
// Base::Console().Message("QGIF::setDrawEdges(%d)\n", b);
|
||||
if (b) {
|
||||
if (state) {
|
||||
setStyle(Qt::DashLine);
|
||||
} else {
|
||||
setStyle(Qt::NoPen); //don't draw face lines, just fill
|
||||
@@ -192,12 +197,12 @@ void QGIFace::setHatchFile(std::string fileSpec)
|
||||
void QGIFace::loadSvgHatch(std::string fileSpec)
|
||||
{
|
||||
QString qfs(QString::fromUtf8(fileSpec.data(), fileSpec.size()));
|
||||
QFile f(qfs);
|
||||
if (!f.open(QFile::ReadOnly | QFile::Text)) {
|
||||
QFile file(qfs);
|
||||
if (!file.open(QFile::ReadOnly | QFile::Text)) {
|
||||
Base::Console().Error("QGIFace could not read %s\n", fileSpec.c_str());
|
||||
return;
|
||||
}
|
||||
m_svgXML = f.readAll();
|
||||
m_svgXML = file.readAll();
|
||||
|
||||
// search in the file for the "stroke" specification in order to find out what declaration style is used
|
||||
// this is necessary to apply a color set by the user to the SVG
|
||||
@@ -211,9 +216,9 @@ void QGIFace::loadSvgHatch(std::string fileSpec)
|
||||
}
|
||||
}
|
||||
|
||||
void QGIFace::setFillMode(QGIFace::fillMode m)
|
||||
void QGIFace::setFillMode(QGIFace::fillMode mode)
|
||||
{
|
||||
m_mode = m;
|
||||
m_mode = mode;
|
||||
if ((m_mode == NoFill) ||
|
||||
(m_mode == PlainFill)) {
|
||||
isHatched(false);
|
||||
@@ -265,13 +270,13 @@ QPen QGIFace::setGeomPen()
|
||||
double QGIFace::getXForm()
|
||||
{
|
||||
//try to keep the pattern the same when View scales
|
||||
auto s = scene();
|
||||
if (s) {
|
||||
auto vs = s->views(); //ptrs to views
|
||||
if (!vs.empty()) {
|
||||
auto v = vs.at(0);
|
||||
auto i = v->transform().inverted();
|
||||
return i.m11();
|
||||
auto ourScene = scene();
|
||||
if (ourScene) {
|
||||
auto viewsAll = ourScene->views(); //ptrs to views
|
||||
if (!viewsAll.empty()) {
|
||||
auto view = viewsAll.at(0);
|
||||
auto iView = view->transform().inverted();
|
||||
return iView.m11();
|
||||
}
|
||||
}
|
||||
return 1.0;
|
||||
@@ -280,15 +285,15 @@ double QGIFace::getXForm()
|
||||
/// remove the children that make up a PAT fill
|
||||
void QGIFace::clearFillItems()
|
||||
{
|
||||
for (auto& f: m_fillItems) {
|
||||
f->setParentItem(nullptr);
|
||||
this->scene()->removeItem(f);
|
||||
delete f;
|
||||
for (auto& fill: m_fillItems) {
|
||||
fill->setParentItem(nullptr);
|
||||
this->scene()->removeItem(fill);
|
||||
delete fill;
|
||||
}
|
||||
}
|
||||
|
||||
/// debugging tool draws a mark at a position on this face
|
||||
void QGIFace::makeMark(double x, double y)
|
||||
void QGIFace::makeMark(double x, double y) // NOLINT readability-identifier-length
|
||||
{
|
||||
QGICMark* cmItem = new QGICMark(-1);
|
||||
cmItem->setParentItem(this);
|
||||
@@ -315,9 +320,9 @@ void QGIFace::buildSvgHatch()
|
||||
double overlayHeight = tilesHigh * hTile;
|
||||
m_svgHatchArea->setRect(0., 0., overlayWidth,-overlayHeight);
|
||||
m_svgHatchArea->centerAt(faceCenter);
|
||||
QByteArray before, after;
|
||||
before = QString::fromStdString(SVGCOLPREFIX + SVGCOLDEFAULT).toUtf8();
|
||||
after = QString::fromStdString(SVGCOLPREFIX + m_svgCol).toUtf8();
|
||||
|
||||
QByteArray before = QString::fromStdString(SVGCOLPREFIX + SVGCOLDEFAULT).toUtf8();
|
||||
QByteArray after = QString::fromStdString(SVGCOLPREFIX + m_svgCol).toUtf8();
|
||||
QByteArray colorXML = m_svgXML.replace(before, after);
|
||||
if (!m_sharedRender->load(colorXML)) {
|
||||
Base::Console().Message("QGIF::buildSvgHatch - failed to load svg string\n");
|
||||
@@ -352,56 +357,138 @@ void QGIFace::clearSvg()
|
||||
hideSvg(true);
|
||||
}
|
||||
|
||||
//this isn't used currently
|
||||
QPixmap QGIFace::textureFromSvg(std::string fileSpec)
|
||||
//! similar to svg hatch, but using pixmaps. we do this because QGraphicsSvgItems are not clipped
|
||||
//! when we export the scene to svg, but pixmaps are clipped.
|
||||
void QGIFace::buildPixHatch()
|
||||
{
|
||||
QString qs(QString::fromStdString(fileSpec));
|
||||
QFileInfo ffi(qs);
|
||||
if (!ffi.isReadable()) {
|
||||
return QPixmap();
|
||||
double wTile = SVGSIZEW * m_fillScale;
|
||||
double hTile = SVGSIZEH * m_fillScale;
|
||||
double faceWidth = m_outline.boundingRect().width();
|
||||
double faceHeight = m_outline.boundingRect().height();
|
||||
double faceOverlaySize = Preferences::svgHatchFactor() * std::max(faceWidth, faceHeight);
|
||||
QPointF faceCenter = m_outline.boundingRect().center();
|
||||
double tilesWide = ceil(faceOverlaySize / wTile);
|
||||
double tilesHigh = ceil(faceOverlaySize / hTile);
|
||||
|
||||
double overlayWidth = tilesWide * wTile;
|
||||
double overlayHeight = tilesHigh * hTile;
|
||||
|
||||
// handle color by brute force find & replace
|
||||
QByteArray before = QString::fromStdString(SVGCOLPREFIX + SVGCOLDEFAULT).toUtf8();
|
||||
QByteArray after = QString::fromStdString(SVGCOLPREFIX + m_svgCol).toUtf8();
|
||||
QByteArray colorXML = m_svgXML.replace(before,after);
|
||||
|
||||
|
||||
// TODO: there is a lot of switching back and forth between svg, QPixmap and QImage here that I
|
||||
// don't really understand.
|
||||
// render svg tile onto a QImage
|
||||
if (!m_sharedRender->load(colorXML)) {
|
||||
Base::Console().Message("QGIF::buildSvgHatch - failed to load svg string\n");
|
||||
return;
|
||||
}
|
||||
QSvgRenderer renderer(qs);
|
||||
QPixmap pixMap(renderer.defaultSize());
|
||||
pixMap.fill(Qt::white); //try Qt::transparent?
|
||||
QPainter painter(&pixMap);
|
||||
renderer.render(&painter); //svg texture -> bitmap
|
||||
return pixMap.scaled(m_fillScale, m_fillScale);
|
||||
|
||||
QImage svgImage(SVGSIZEW, SVGSIZEH, QImage::Format_ARGB32);
|
||||
svgImage.fill(Qt::transparent);
|
||||
QPainter painter(&svgImage);
|
||||
if (svgImage.isNull()) {
|
||||
Base::Console().Error("QGIF::buildPixHatch - svgImage is null\n");
|
||||
return;
|
||||
}
|
||||
|
||||
m_sharedRender->render(&painter);
|
||||
|
||||
// convert the QImage into a QPixmap
|
||||
QPixmap tilePixmap(SVGSIZEW, SVGSIZEH);
|
||||
tilePixmap = QPixmap::fromImage(svgImage);
|
||||
tilePixmap = tilePixmap.scaled(wTile, hTile);
|
||||
if (tilePixmap.isNull()) {
|
||||
Base::Console().Error("QGIF::buildPixHatch - tilePixmap is null\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// layout a field of bitmap tiles big enough to cover this face onto a Qimage
|
||||
QImage tileField(overlayWidth, overlayHeight, QImage::Format_ARGB32);
|
||||
QPointF fieldCenter(overlayWidth / 2.0, overlayHeight / 2.0);
|
||||
|
||||
tileField.fill(Qt::transparent);
|
||||
QPainter painter2(&tileField);
|
||||
QPainter::RenderHints hints = painter2.renderHints();
|
||||
hints = hints & QPainter::Antialiasing;
|
||||
painter2.setRenderHints(hints);
|
||||
QPainterPath clipper = path();
|
||||
QPointF offset = (fieldCenter - faceCenter);
|
||||
clipper.translate(offset);
|
||||
painter2.setClipPath(clipper);
|
||||
|
||||
long int tileCount = 0;
|
||||
for (int iw = 0; iw < int(tilesWide); iw++) {
|
||||
for (int ih = 0; ih < int(tilesHigh); ih++) {
|
||||
painter2.drawPixmap(QRectF(iw * wTile, ih * hTile, wTile, hTile), //target rect
|
||||
tilePixmap,
|
||||
QRectF(0, 0, wTile, hTile)); //source rect
|
||||
tileCount++;
|
||||
if (tileCount > m_maxTile) {
|
||||
Base::Console().Warning("Pixmap tile count exceeded: %ld\n",tileCount);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tileCount > m_maxTile) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// turn the QImage field into a pixmap
|
||||
QPixmap fieldPixmap(overlayWidth, overlayHeight);
|
||||
fieldPixmap = QPixmap::fromImage(tileField);
|
||||
|
||||
// TODO: figure out how to rotate the pixmap without it looking terrible - far worse than the unrotated pixmap. svg hatch exported to svg will not be shown rotated
|
||||
// QTransform xFormPixmap;
|
||||
// xFormPixmap.rotate(m_hatchRotation);
|
||||
// xFormPixmap.translate(getHatchOffset().x, getHatchOffset().y);
|
||||
// m_imageSvgHatchArea->load(fieldPixmap.transformed(xFormPixmap));
|
||||
|
||||
QPixmap nothing;
|
||||
m_imageSvgHatchArea->setPixmap(nothing);
|
||||
m_imageSvgHatchArea->load(fieldPixmap);
|
||||
m_imageSvgHatchArea->centerAt(faceCenter);
|
||||
|
||||
}
|
||||
|
||||
void QGIFace::setHatchColor(App::Color c)
|
||||
|
||||
void QGIFace::setHatchColor(App::Color color)
|
||||
{
|
||||
m_svgCol = c.asHexString();
|
||||
m_geomColor = c.asValue<QColor>();
|
||||
m_svgCol = color.asHexString();
|
||||
m_geomColor = color.asValue<QColor>();
|
||||
}
|
||||
|
||||
void QGIFace::setHatchScale(double s)
|
||||
void QGIFace::setHatchScale(double scale)
|
||||
{
|
||||
m_fillScale = s;
|
||||
m_fillScale = scale;
|
||||
}
|
||||
|
||||
/// turn svg tiles on or off. QtSvg does not handle clipping,
|
||||
/// so we must be able to turn the hatching on/off when exporting a face with an
|
||||
/// svg hatch. Otherwise the full tile pattern is shown in the export.
|
||||
/// NOTE: there appears to have been a change in Qt that it now clips svg items
|
||||
void QGIFace::hideSvg(bool b)
|
||||
void QGIFace::hideSvg(bool state)
|
||||
{
|
||||
m_hideSvgTiles = b;
|
||||
m_hideSvgTiles = state;
|
||||
}
|
||||
|
||||
|
||||
/// create a QPixmap from a bitmap file. The QPixmap will be used as a QBrush
|
||||
/// texture.
|
||||
QPixmap QGIFace::textureFromBitmap(std::string fileSpec)
|
||||
QPixmap QGIFace::textureFromBitmap(std::string fileSpec) const
|
||||
{
|
||||
QPixmap pix;
|
||||
|
||||
QString qfs(QString::fromUtf8(fileSpec.data(), fileSpec.size()));
|
||||
QFile f(qfs);
|
||||
if (!f.open(QFile::ReadOnly)) {
|
||||
QFile file(qfs);
|
||||
if (!file.open(QFile::ReadOnly)) {
|
||||
Base::Console().Error("QGIFace could not read %s\n", fileSpec.c_str());
|
||||
return pix;
|
||||
}
|
||||
QByteArray bytes = f.readAll();
|
||||
QByteArray bytes = file.readAll();
|
||||
pix.loadFromData(bytes);
|
||||
if (m_hatchRotation != 0.0) {
|
||||
QTransform rotator;
|
||||
@@ -411,14 +498,14 @@ QPixmap QGIFace::textureFromBitmap(std::string fileSpec)
|
||||
return pix;
|
||||
}
|
||||
|
||||
void QGIFace::setLineWeight(double w) {
|
||||
m_geomWeight = w;
|
||||
void QGIFace::setLineWeight(double weight) {
|
||||
m_geomWeight = weight;
|
||||
}
|
||||
|
||||
void QGIFace::getParameters()
|
||||
{
|
||||
m_maxSeg = Preferences::getPreferenceGroup("PAT")->GetInt("MaxSeg", 10000l);
|
||||
m_maxTile = Preferences::getPreferenceGroup("Decorations")->GetInt("MaxSVGTile", 10000l);
|
||||
m_maxSeg = Preferences::getPreferenceGroup("PAT")->GetInt("MaxSeg", MAXSEGMENT);
|
||||
m_maxTile = Preferences::getPreferenceGroup("Decorations")->GetInt("MaxSVGTile", MAXTILES);
|
||||
}
|
||||
|
||||
QRectF QGIFace::boundingRect() const
|
||||
@@ -430,3 +517,13 @@ QPainterPath QGIFace::shape() const
|
||||
{
|
||||
return path();
|
||||
}
|
||||
|
||||
bool QGIFace::exporting() const
|
||||
{
|
||||
auto tdScene = dynamic_cast<QGSPage*>(scene());
|
||||
if (!tdScene) {
|
||||
return false;
|
||||
}
|
||||
return tdScene->getExportingSvg();
|
||||
}
|
||||
|
||||
|
||||
@@ -43,9 +43,14 @@ class QGCustomSvg;
|
||||
class QGCustomRect;
|
||||
class QGCustomImage;
|
||||
|
||||
const double SVGSIZEW = 64.0; //width and height of standard FC SVG pattern
|
||||
const double SVGSIZEH = 64.0;
|
||||
const std::string SVGCOLDEFAULT = "#000000";
|
||||
constexpr int SVGSIZEW{64}; //width and height of standard FC SVG pattern
|
||||
constexpr int SVGSIZEH{64};
|
||||
constexpr uint32_t COLWHITE{0xfffff}; // white
|
||||
constexpr int ALPHALOW{0};
|
||||
constexpr int ALPHAHIGH{255};
|
||||
constexpr long int MAXSEGMENT{10000L};
|
||||
constexpr long int MAXTILES{10000L};
|
||||
const std::string SVGCOLDEFAULT = "#000000"; // black
|
||||
|
||||
class QGIFace : public QGIPrimPath
|
||||
{
|
||||
@@ -68,35 +73,37 @@ public:
|
||||
PlainFill
|
||||
};
|
||||
std::string SVGCOLPREFIX = ""; // will be determined on runtime
|
||||
|
||||
int getProjIndex() const { return projIndex; }
|
||||
|
||||
void draw();
|
||||
void setPrettyNormal() override;
|
||||
void setPrettyPre() override;
|
||||
void setPrettySel() override;
|
||||
void setDrawEdges(bool b);
|
||||
void setDrawEdges(bool state);
|
||||
virtual void setOutline(const QPainterPath& path);
|
||||
|
||||
//shared fill parms
|
||||
void isHatched(bool s) {m_isHatched = s; }
|
||||
void isHatched(bool state) {m_isHatched = state; }
|
||||
bool isHatched() {return m_isHatched;}
|
||||
void setFillMode(fillMode m);
|
||||
void setFillMode(fillMode mode);
|
||||
|
||||
//general hatch parms & methods
|
||||
void setHatchColor(App::Color c);
|
||||
void setHatchScale(double s);
|
||||
void setHatchColor(App::Color color);
|
||||
void setHatchScale(double scale);
|
||||
|
||||
//svg fill parms & methods
|
||||
void setHatchFile(std::string fileSpec);
|
||||
void loadSvgHatch(std::string fileSpec);
|
||||
void buildSvgHatch();
|
||||
void hideSvg(bool b);
|
||||
void hideSvg(bool state);
|
||||
void clearSvg();
|
||||
|
||||
//tiled pixmap fill from svg
|
||||
void buildPixHatch();
|
||||
|
||||
//PAT fill parms & methods
|
||||
void setGeomHatchWeight(double w) { m_geomWeight = w; }
|
||||
void setLineWeight(double w);
|
||||
void setGeomHatchWeight(double weight) { m_geomWeight = weight; }
|
||||
void setLineWeight(double weight);
|
||||
|
||||
void clearLineSets();
|
||||
void addLineSet(TechDraw::LineSet& ls);
|
||||
@@ -104,13 +111,10 @@ public:
|
||||
|
||||
void lineSetToFillItems(TechDraw::LineSet& ls);
|
||||
QGraphicsPathItem* geomToLine(TechDraw::BaseGeomPtr base, TechDraw::LineSet& ls);
|
||||
// QGraphicsPathItem* geomToOffsetLine(TechDraw::BaseGeomPtr base, double offset, const TechDraw::LineSet& ls);
|
||||
QGraphicsPathItem* geomToStubbyLine(TechDraw::BaseGeomPtr base, double offset, TechDraw::LineSet& ls);
|
||||
QGraphicsPathItem* lineFromPoints(Base::Vector3d start, Base::Vector3d end, TechDraw::DashSpec ds);
|
||||
|
||||
//bitmap texture fill parms method
|
||||
QPixmap textureFromBitmap(std::string fileSpec);
|
||||
QPixmap textureFromSvg(std::string fillSpec);
|
||||
QPixmap textureFromBitmap(std::string fileSpec) const;
|
||||
|
||||
//Qt uses clockwise degrees
|
||||
void setHatchRotation(double degrees) { m_hatchRotation = -degrees; }
|
||||
@@ -120,39 +124,46 @@ public:
|
||||
Base::Vector3d getHatchOffset() { return m_hatchOffset; }
|
||||
|
||||
protected:
|
||||
void makeMark(double x, double y);
|
||||
void makeMark(double x, double y); // NOLINT readability-identifier-length
|
||||
double getXForm();
|
||||
void getParameters();
|
||||
|
||||
std::vector<double> offsetDash(const std::vector<double> dv, const double offset);
|
||||
QPainterPath dashedPPath(const std::vector<double> dv, const Base::Vector3d start, const Base::Vector3d end);
|
||||
double dashRemain(const std::vector<double> dv, const double offset);
|
||||
double calcOffset(TechDraw::BaseGeomPtr g, TechDraw::LineSet ls);
|
||||
int projIndex; //index of face in Projection. -1 for SectionFace.
|
||||
QGCustomRect* m_svgHatchArea;
|
||||
double calcOffset(TechDraw::BaseGeomPtr geom, TechDraw::LineSet ls);
|
||||
|
||||
QByteArray m_svgXML;
|
||||
std::string m_svgCol;
|
||||
std::string m_fileSpec; //for svg & bitmaps
|
||||
|
||||
double m_fillScale;
|
||||
bool m_isHatched;
|
||||
QGIFace::fillMode m_mode;
|
||||
|
||||
QPen setGeomPen();
|
||||
std::vector<double> decodeDashSpec(TechDraw::DashSpec d);
|
||||
std::vector<QGraphicsPathItem*> m_fillItems;
|
||||
std::vector<TechDraw::LineSet> m_lineSets;
|
||||
std::vector<TechDraw::DashSpec> m_dashSpecs;
|
||||
long int m_segCount;
|
||||
long int m_maxSeg;
|
||||
long int m_maxTile;
|
||||
std::vector<double> decodeDashSpec(TechDraw::DashSpec dash);
|
||||
|
||||
bool m_hideSvgTiles;
|
||||
|
||||
bool multiselectEligible() override { return true; }
|
||||
|
||||
bool exporting() const;
|
||||
|
||||
|
||||
private:
|
||||
std::vector<QGraphicsPathItem*> m_fillItems;
|
||||
std::vector<TechDraw::LineSet> m_lineSets;
|
||||
std::vector<TechDraw::DashSpec> m_dashSpecs;
|
||||
long int m_segCount{0};
|
||||
long int m_maxSeg{0};
|
||||
long int m_maxTile{0};
|
||||
|
||||
bool m_hideSvgTiles{false};
|
||||
int projIndex; //index of face in Projection. -1 for SectionFace.
|
||||
|
||||
QGCustomRect* m_svgHatchArea;
|
||||
QGCustomImage* m_imageSvgHatchArea;
|
||||
|
||||
QByteArray m_svgXML;
|
||||
std::string m_svgCol{SVGCOLDEFAULT};
|
||||
std::string m_fileSpec; //for svg & bitmaps
|
||||
|
||||
double m_fillScale{1.0};
|
||||
bool m_isHatched{false};
|
||||
QGIFace::fillMode m_mode;
|
||||
QPixmap m_texture; //
|
||||
|
||||
QPainterPath m_outline; //
|
||||
@@ -160,11 +171,11 @@ private:
|
||||
QPainterPath m_geomhatch; //crosshatch fill lines
|
||||
|
||||
QColor m_geomColor; //color for crosshatch lines
|
||||
double m_geomWeight; //lineweight for crosshatch lines
|
||||
double m_geomWeight{0.5}; //lineweight for crosshatch lines
|
||||
|
||||
QColor m_defFaceColor;
|
||||
|
||||
double m_hatchRotation;
|
||||
double m_hatchRotation{0.0};
|
||||
Base::Vector3d m_hatchOffset;
|
||||
|
||||
QSvgRenderer *m_sharedRender;
|
||||
|
||||
@@ -1056,13 +1056,12 @@ void QGIViewPart::drawBreakLines()
|
||||
QGIBreakLine* breakLine = new QGIBreakLine();
|
||||
addToGroup(breakLine);
|
||||
|
||||
Base::Vector3d direction = dbv->directionFromObj(*breakObj);
|
||||
direction.Normalize();
|
||||
Base::Vector3d direction = dbv->guiDirectionFromObj(*breakObj);
|
||||
breakLine->setDirection(direction);
|
||||
// the bounds describe two corners of the removed area
|
||||
// the bounds describe two corners of the removed area in the view
|
||||
std::pair<Base::Vector3d, Base::Vector3d> bounds = dbv->breakBoundsFromObj(*breakObj);
|
||||
// the bounds are in 3d form, so we need to invert & rez them
|
||||
Base::Vector3d topLeft = Rez::guiX(DU::invertY(bounds.first));
|
||||
Base::Vector3d topLeft = Rez::guiX(DU::invertY(bounds.first));
|
||||
Base::Vector3d bottomRight = Rez::guiX(DU::invertY(bounds.second));
|
||||
breakLine->setBounds(topLeft, bottomRight);
|
||||
breakLine->setPos(0.0, 0.0);
|
||||
|
||||
@@ -930,8 +930,10 @@ void QGSPage::redraw1View(TechDraw::DrawView* dView)
|
||||
|
||||
// RichTextAnno needs to know when it is rendering an Svg as the font size
|
||||
// is handled differently in Svg compared to the screen or Pdf.
|
||||
// Also true of QGraphicsSvgItems.
|
||||
void QGSPage::setExportingSvg(bool enable)
|
||||
{
|
||||
m_exportingSvg = enable;
|
||||
QList<QGraphicsItem*> sceneItems = items();
|
||||
for (auto& qgi : sceneItems) {
|
||||
QGIRichAnno* qgiRTA = dynamic_cast<QGIRichAnno*>(qgi);
|
||||
|
||||
@@ -129,6 +129,7 @@ public:
|
||||
TechDraw::DrawPage* getDrawPage();
|
||||
|
||||
void setExportingSvg(bool enable);
|
||||
bool getExportingSvg() { return m_exportingSvg; }
|
||||
virtual void refreshViews();
|
||||
|
||||
/// Renders the page to SVG with filename.
|
||||
@@ -145,6 +146,8 @@ protected:
|
||||
private:
|
||||
QGITemplate* pageTemplate;
|
||||
ViewProviderPage* m_vpPage;
|
||||
|
||||
bool m_exportingSvg{false};
|
||||
};
|
||||
|
||||
}// namespace TechDrawGui
|
||||
|
||||
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
@@ -0,0 +1,15 @@
|
||||
This folder (`locale`) contains translations for [TechDraw workbench templates](https://wiki.freecad.org/TechDraw_Templates) in the parent `Templates` folder.
|
||||
The name of each `locale` subfolder represents a language, which follows [IETF BCP 47 standardized codes](https://en.wikipedia.org/wiki/IETF_language_tag). The original TechDraw templates in the parent folder are written using American English (`en-US`).
|
||||
|
||||
As such, the most basic name for a locale subfolder will include an [ISO 639 language code](https://en.wikipedia.org/wiki/ISO_639) (e.g. `de` for German). If it's necessary, additional subtags can be added to describe language variants. For instance variants spoken in a particular country, or a specific script. Those subtags are combinable and are based in other standards.
|
||||
|
||||
The most common additional subtag is an additional country code to describe a regional variant of the language (e.g. `de-DE` for German spoken in Germany, `es-AR` for Spanish spoken in Argentina, or `zh-CN` for Simplified Chinese in Mainland China). Country subtags are based on [the ISO 3166-1 standard's country codes](https://en.wikipedia.org/wiki/ISO_3166-1).
|
||||
|
||||
To add a translation:
|
||||
|
||||
1. Add a folder named `ll` or `ll-CC` (where `ll` is a 2-letter or 3-letter ISO 639 language code, and `CC` is a 2-letter ISO 3166-1 country code).
|
||||
2. Copy over the TechDraw templates in the parent `Templates` folder that you want to translate to your new folder.
|
||||
3. [Translate away!](https://wiki.freecad.org/TechDraw_Templates)
|
||||
4. [Submit a PR (GitHub Pull Request)](https://freecad.github.io/DevelopersHandbook/gettingstarted/#submitting-a-pr) to get your Tech Draw template translations included in FreeCAD.
|
||||
|
||||
Use the [FreeCAD forum](https://forum.freecad.org/) if you need further help.
|
||||
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |