Files
create/src/Mod/PartDesign/Gui/Utils.cpp
Markus Reitböck 5b6a0b1852 PartDesign: use CMake to generate precompiled headers on all platforms
"Professional CMake" book suggest the following:

"Targets should build successfully with or without compiler support for precompiled headers. It
 should be considered an optimization, not a requirement. In particular, do not explicitly include a
 precompile header (e.g. stdafx.h) in the source code, let CMake force-include an automatically
 generated precompile header on the compiler command line instead. This is more portable across
 the major compilers and is likely to be easier to maintain. It will also avoid warnings being
 generated from certain code checking tools like iwyu (include what you use)."

Therefore, removed the "#include <PreCompiled.h>" from sources, also
there is no need for the "#ifdef _PreComp_" anymore
2025-09-23 22:39:36 +02:00

601 lines
24 KiB
C++

/***************************************************************************
* Copyright (C) 2015 Alexander Golubev (Fat-Zer) <fatzer2@gmail.com> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#include <QMessageBox>
# include <gp_Pln.hxx>
# include <Precision.hxx>
#include <App/Origin.h>
#include <App/Datums.h>
#include <App/Part.h>
#include <Gui/Application.h>
#include <Gui/CommandT.h>
#include <Gui/MainWindow.h>
#include <Gui/MDIView.h>
#include <Mod/PartDesign/App/Body.h>
#include <Mod/PartDesign/App/Feature.h>
#include <Mod/PartDesign/App/FeatureSketchBased.h>
#include <Mod/Sketcher/App/SketchObject.h>
#include "Utils.h"
#include "DlgActiveBody.h"
#include "ReferenceSelection.h"
#include "WorkflowManager.h"
FC_LOG_LEVEL_INIT("PartDesignGui",true,true)
//===========================================================================
// Helper for Body
//===========================================================================
using namespace Attacher;
namespace PartDesignGui {
// TODO: Refactor DocumentObjectItem::getSubName() that has similar logic
App::DocumentObject* getParent(App::DocumentObject* obj, std::string& subname)
{
auto inlist = obj->getInList();
for (auto it : inlist) {
if (it->hasExtension(App::GeoFeatureGroupExtension::getExtensionClassTypeId())) {
std::string parent;
parent += obj->getNameInDocument();
parent += '.';
subname = parent + subname;
return getParent(it, subname);
}
}
return obj;
}
bool setEdit(App::DocumentObject *obj, PartDesign::Body *body) {
if (!obj || !obj->isAttachedToDocument()) {
FC_ERR("invalid object");
return false;
}
if (!body) {
body = getBodyFor(obj, false);
if (!body) {
FC_ERR("no body found");
return false;
}
}
auto *activeView = Gui::Application::Instance->activeView();
if (!activeView)
return false;
App::DocumentObject *parent = nullptr;
std::string subname;
auto activeBody = activeView->getActiveObject<PartDesign::Body*>(PDBODYKEY, &parent, &subname);
if (activeBody != body) {
parent = obj;
subname.clear();
}
else {
subname += obj->getNameInDocument();
subname += '.';
}
Gui::cmdGuiDocument(parent, std::ostringstream() << "setEdit("
<< Gui::Command::getObjectCmd(parent)
<< ", 0, '" << subname << "')");
return true;
}
/*!
* \brief Return active body or show a warning message.
* If \a autoActivate is true (the default) then if there is
* only single body in the document it will be activated.
* \param messageIfNot
* \param autoActivate
* \return Body
*/
PartDesign::Body *getBody(bool messageIfNot, bool autoActivate, bool assertModern,
App::DocumentObject **topParent, std::string *subname)
{
PartDesign::Body * activeBody = nullptr;
Gui::MDIView *activeView = Gui::Application::Instance->activeView();
if (activeView) {
auto doc = activeView->getAppDocument();
bool singleBodyDocument = doc->countObjectsOfType<PartDesign::Body>() == 1;
if (assertModern) {
activeBody = activeView->getActiveObject<PartDesign::Body*>(PDBODYKEY,topParent,subname);
if (!activeBody && singleBodyDocument && autoActivate) {
auto bodies = doc->getObjectsOfType(PartDesign::Body::getClassTypeId());
App::DocumentObject *body = nullptr;
if(bodies.size()==1) {
body = bodies[0];
activeBody = makeBodyActive(body, doc, topParent, subname);
}
}
if (!activeBody && messageIfNot) {
DlgActiveBody dia(
Gui::getMainWindow(),
doc,
QObject::tr("To use Part Design, an active body is required in the document. "
"Activate a body (double-click) or create a new one."
"\n\nFor legacy documents with Part Design objects lacking a body, "
"use the migrate function in Part Design to place them into a body."));
if (dia.exec() == QDialog::DialogCode::Accepted)
activeBody = dia.getActiveBody();
}
}
}
return activeBody;
}
PartDesign::Body * makeBodyActive(App::DocumentObject *body, App::Document *doc,
App::DocumentObject **topParent,
std::string *subname)
{
App::DocumentObject *parent = nullptr;
std::string sub;
for(auto &v : body->getParents()) {
if(v.first->getDocument()!=doc)
continue;
if(parent) {
body = nullptr;
break;
}
parent = v.first;
sub = v.second;
}
if(body) {
auto _doc = parent?parent->getDocument():body->getDocument();
Gui::cmdGuiDocument(_doc, std::stringstream()
<< "ActiveView.setActiveObject('" << PDBODYKEY
<< "'," << Gui::Command::getObjectCmd(parent?parent:body)
<< ",'" << sub << "')");
return Gui::Application::Instance->activeView()->
getActiveObject<PartDesign::Body*>(PDBODYKEY,topParent,subname);
}
return dynamic_cast<PartDesign::Body*>(body);
}
void needActiveBodyError()
{
QMessageBox::warning( Gui::getMainWindow(),
QObject::tr("Active Body Required"),
QObject::tr("To create a new Part Design object, an active body is required in the document. "
"Activate an existing body (double-click) or create a new one."));
}
PartDesign::Body * makeBody(App::Document *doc)
{
// This is intended as a convenience when starting a new document.
auto bodyName( doc->getUniqueObjectName("Body") );
Gui::Command::doCommand( Gui::Command::Doc,
"App.getDocument('%s').addObject('PartDesign::Body','%s')",
doc->getName(), bodyName.c_str() );
auto body = dynamic_cast<PartDesign::Body*>(doc->getObject(bodyName.c_str()));
if(body)
makeBodyActive(body, doc);
return body;
}
PartDesign::Body *getBodyFor(const App::DocumentObject* obj, bool messageIfNot,
bool autoActivate, bool assertModern,
App::DocumentObject **topParent, std::string *subname)
{
if(!obj)
return nullptr;
PartDesign::Body * rv = getBody(/*messageIfNot =*/false, autoActivate, assertModern, topParent, subname);
if (rv && rv->hasObject(obj))
return rv;
rv = PartDesign::Body::findBodyOf(obj);
if (rv) {
return rv;
}
if (messageIfNot){
QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Feature is not in a body"),
QObject::tr("In order to use this feature it needs to belong to a body object in the document."));
}
return nullptr;
}
App::Part* getActivePart() {
Gui::MDIView *activeView = Gui::Application::Instance->activeView();
if ( activeView ) {
return activeView->getActiveObject<App::Part*> (PARTKEY);
} else {
return nullptr;
}
}
App::Part* getPartFor(const App::DocumentObject* obj, bool messageIfNot) {
if(!obj)
return nullptr;
PartDesign::Body* body = getBodyFor(obj, false);
if(body)
obj = body;
//get the part
for(App::Part* p : obj->getDocument()->getObjectsOfType<App::Part>()) {
if(p->hasObject(obj)) {
return p;
}
}
if (messageIfNot){
QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Feature is not in a part"),
QObject::tr("In order to use this feature it needs to belong to a part object in the document."));
}
return nullptr;
}
//static void buildDefaultPartAndBody(const App::Document* doc)
//{
// // This adds both the base planes and the body
// std::string PartName = doc->getUniqueObjectName("Part");
// //// create a PartDesign Part for now, can be later any kind of Part or an empty one
// Gui::Command::addModule(Gui::Command::Doc, "PartDesignGui");
// Gui::Command::doCommand(Gui::Command::Doc, "App.activeDocument().Tip = App.activeDocument().addObject('App::Part','%s')", PartName.c_str());
// Gui::Command::doCommand(Gui::Command::Doc, "PartDesignGui.setUpPart(App.activeDocument().%s)", PartName.c_str());
// Gui::Command::doCommand(Gui::Command::Gui, "Gui.activeView().setActiveObject('Part',App.activeDocument().%s)", PartName.c_str());
//}
void fixSketchSupport (Sketcher::SketchObject* sketch)
{
App::DocumentObject* support = sketch->AttachmentSupport.getValue();
if (support)
return; // Sketch is on a face of a solid, do nothing
const App::Document* doc = sketch->getDocument();
PartDesign::Body *body = getBodyFor(sketch, /*messageIfNot*/ false);
if (!body) {
throw Base::RuntimeError ("Could not find a body for the sketch");
}
// Get the Origin for the body
App::Origin *origin = body->getOrigin (); // May throw by itself
Base::Placement plm = sketch->Placement.getValue();
Base::Vector3d pnt = plm.getPosition();
// Currently we only handle positions that are parallel to the base planes
Base::Rotation rot = plm.getRotation();
Base::Vector3d sketchVector(0,0,1);
rot.multVec(sketchVector, sketchVector);
bool reverseSketch = (sketchVector.x + sketchVector.y + sketchVector.z) < 0.0 ;
if (reverseSketch) sketchVector *= -1.0;
App::Plane *plane =nullptr;
if (sketchVector == Base::Vector3d(0,0,1))
plane = origin->getXY ();
else if (sketchVector == Base::Vector3d(0,1,0))
plane = origin->getXZ ();
else if (sketchVector == Base::Vector3d(1,0,0))
plane = origin->getYZ ();
else {
throw Base::ValueError("Sketch plane cannot be migrated");
}
assert (plane);
// Find the normal distance from origin to the sketch plane
gp_Pln pln(gp_Pnt (pnt.x, pnt.y, pnt.z), gp_Dir(sketchVector.x, sketchVector.y, sketchVector.z));
double offset = pln.Distance(gp_Pnt(0,0,0));
// TODO Issue a message if sketch have coordinates offset inside the plain (2016-08-15, Fat-Zer)
if (fabs(offset) < Precision::Confusion()) {
// One of the base planes
FCMD_OBJ_CMD(sketch,"AttachmentSupport = (" << Gui::Command::getObjectCmd(plane) << ",[''])");
FCMD_OBJ_CMD(sketch,"MapReversed = " << (reverseSketch ? "True" : "False"));
FCMD_OBJ_CMD(sketch,"MapMode = '" << Attacher::AttachEngine::getModeName(Attacher::mmFlatFace) << "'");
} else {
// Offset to base plane
// Find out which direction we need to offset
double a = sketchVector.GetAngle(pnt);
if ((a < -std::numbers::pi/2) || (a > std::numbers::pi/2))
offset *= -1.0;
std::string Datum = doc->getUniqueObjectName("DatumPlane");
FCMD_DOC_CMD(doc,"addObject('PartDesign::Plane','"<<Datum<<"')");
auto obj = doc->getObject(Datum.c_str());
FCMD_OBJ_CMD(obj,"AttachmentSupport = [(" << Gui::Command::getObjectCmd(plane) << ",'')]");
FCMD_OBJ_CMD(obj,"MapMode = '" << AttachEngine::getModeName(Attacher::mmFlatFace) << "'");
FCMD_OBJ_CMD(obj,"AttachmentOffset.Base.z = " << offset);
FCMD_OBJ_CMD(body,"insertObject("<<Gui::Command::getObjectCmd(obj)<<','<<
Gui::Command::getObjectCmd(sketch)<<")");
FCMD_OBJ_CMD(sketch,"AttachmentSupport = (" << Gui::Command::getObjectCmd(obj) << ",[''])");
FCMD_OBJ_CMD(sketch,"MapReversed = " << (reverseSketch ? "True" : "False"));
FCMD_OBJ_CMD(sketch,"MapMode = '" << Attacher::AttachEngine::getModeName(Attacher::mmFlatFace) << "'");
}
}
bool isPartDesignAwareObjecta (App::DocumentObject *obj, bool respectGroups = false ) {
return (obj->isDerivedFrom( PartDesign::Feature::getClassTypeId () ) ||
PartDesign::Body::isAllowed ( obj ) ||
obj->isDerivedFrom ( PartDesign::Body::getClassTypeId () ) ||
( respectGroups && (
obj->hasExtension (App::GeoFeatureGroupExtension::getExtensionClassTypeId () ) ||
obj->hasExtension (App::GroupExtension::getExtensionClassTypeId () )
) ) );
}
bool isAnyNonPartDesignLinksTo ( PartDesign::Feature *feature, bool respectGroups ) {
App::Document *doc = feature->getDocument();
for ( const auto & obj: doc->getObjects () ) {
if ( !isPartDesignAwareObjecta ( obj, respectGroups ) ) {
std::vector <App::Property *> properties;
obj->getPropertyList ( properties );
for (auto prop: properties ) {
if ( prop->isDerivedFrom ( App::PropertyLink::getClassTypeId() ) ) {
if ( static_cast <App::PropertyLink *> ( prop )->getValue () == feature ) {
return true;
}
} else if ( prop->isDerivedFrom ( App::PropertyLinkSub::getClassTypeId() ) ) {
if ( static_cast <App::PropertyLinkSub *> ( prop )->getValue () == feature ) {
return true;
}
} else if ( prop->isDerivedFrom ( App::PropertyLinkList::getClassTypeId() ) ) {
auto values = static_cast <App::PropertyLinkList *> ( prop )->getValues ();
if ( std::find ( values.begin (), values.end (), feature ) != values.end() ) {
return true;
}
} else if ( prop->isDerivedFrom ( App::PropertyLinkSubList::getClassTypeId() ) ) {
auto values = static_cast <App::PropertyLinkSubList *> ( prop )->getValues ();
if ( std::find ( values.begin (), values.end (), feature ) != values.end() ) {
return true;
}
}
}
}
}
return false;
}
void relinkToBody (PartDesign::Feature *feature) {
App::Document *doc = feature->getDocument();
PartDesign::Body *body = PartDesign::Body::findBodyOf ( feature );
if (!body) {
throw Base::RuntimeError ("Could not find a body for the feature");
}
for ( const auto & obj: doc->getObjects () ) {
if ( !isPartDesignAwareObjecta ( obj ) ) {
std::vector <App::Property *> properties;
obj->getPropertyList ( properties );
for (auto prop: properties ) {
std::string valueStr;
if ( prop->isDerivedFrom ( App::PropertyLink::getClassTypeId() ) ) {
App::PropertyLink *propLink = static_cast <App::PropertyLink *> ( prop );
if ( propLink->getValue() != feature ) {
continue;
}
valueStr = Gui::Command::getObjectCmd(body);
} else if ( prop->isDerivedFrom ( App::PropertyLinkSub::getClassTypeId() ) ) {
App::PropertyLinkSub *propLink = static_cast <App::PropertyLinkSub *> ( prop );
if ( propLink->getValue() != feature ) {
continue;
}
valueStr = buildLinkSubPythonStr ( body, propLink->getSubValues() );
} else if ( prop->isDerivedFrom ( App::PropertyLinkList::getClassTypeId() ) ) {
App::PropertyLinkList *propLink = static_cast <App::PropertyLinkList *> ( prop );
std::vector <App::DocumentObject *> linkList = propLink->getValues ();
bool valueChanged=false;
for (auto & link : linkList ) {
if ( link == feature ) {
link = body;
valueChanged = true;
}
}
if ( valueChanged ) {
valueStr = buildLinkListPythonStr ( linkList );
// TODO Issue some message here due to it likely will break something
// (2015-08-13, Fat-Zer)
}
} else if ( prop->isDerivedFrom ( App::PropertyLinkSub::getClassTypeId() ) ) {
App::PropertyLinkSubList *propLink = static_cast <App::PropertyLinkSubList *> ( prop );
std::vector <App::DocumentObject *> linkList = propLink->getValues ();
bool valueChanged=false;
for (auto & link : linkList ) {
if ( link == feature ) {
link = body;
valueChanged = true;
}
}
if ( valueChanged ) {
valueStr = buildLinkSubListPythonStr ( linkList, propLink->getSubValues() );
// TODO Issue some message here due to it likely will break something
// (2015-08-13, Fat-Zer)
}
}
if ( !valueStr.empty () && prop->hasName()) {
FCMD_OBJ_CMD(obj,prop->getName() << '=' << valueStr);
}
}
}
}
}
bool isFeatureMovable(App::DocumentObject* const feat)
{
if (!feat) {
return false;
}
if (auto prim = dynamic_cast<PartDesign::Feature*>(feat)) {
App::DocumentObject* bf = prim->BaseFeature.getValue();
if (bf) {
return false;
}
}
if (auto prim = dynamic_cast<PartDesign::ProfileBased*>(feat)) {
auto sk = prim->getVerifiedSketch(true);
if (!isFeatureMovable(sk)) {
return false;
}
if (auto prop = dynamic_cast<App::PropertyLinkList*>(prim->getPropertyByName("Sections"))) {
if (std::any_of(prop->getValues().begin(), prop->getValues().end(), [](App::DocumentObject* obj) {
return !isFeatureMovable(obj);
})) {
return false;
}
}
if (auto prop = dynamic_cast<App::PropertyLinkSubList*>(prim->getPropertyByName("Sections"))) {
if (std::any_of(prop->getValues().begin(), prop->getValues().end(), [](App::DocumentObject* obj) {
return !isFeatureMovable(obj);
})) {
return false;
}
}
if (auto prop = dynamic_cast<App::PropertyLinkSub*>(prim->getPropertyByName("ReferenceAxis"))) {
App::DocumentObject* axis = prop->getValue();
if (axis && !isFeatureMovable(axis)) {
return false;
}
}
if (auto prop = dynamic_cast<App::PropertyLinkSub*>(prim->getPropertyByName("Spine"))) {
App::DocumentObject* spine = prop->getValue();
if (spine && !isFeatureMovable(spine)) {
return false;
}
}
if (auto prop = dynamic_cast<App::PropertyLinkSub*>(prim->getPropertyByName("AuxiliarySpine"))) {
App::DocumentObject* auxSpine = prop->getValue();
if (auxSpine && !isFeatureMovable(auxSpine)) {
return false;
}
}
}
if (feat->hasExtension(Part::AttachExtension::getExtensionClassTypeId())) {
auto attachable = feat->getExtensionByType<Part::AttachExtension>();
App::DocumentObject* support = attachable->AttachmentSupport.getValue();
if (support && !support->isDerivedFrom<App::DatumElement>()) {
return false;
}
}
return true;
}
std::vector<App::DocumentObject*> collectMovableDependencies(std::vector<App::DocumentObject*>& features)
{
std::set<App::DocumentObject*> unique_objs;
for (auto const &feat : features) {
// Get sketches and datums from profile based features
if (auto prim = dynamic_cast<PartDesign::ProfileBased*>(feat)) {
Part::Part2DObject* sk = prim->getVerifiedSketch(true);
if (sk) {
unique_objs.insert(sk);
}
if (auto prop = dynamic_cast<App::PropertyLinkList*>(prim->getPropertyByName("Sections"))) {
for (App::DocumentObject* obj : prop->getValues()) {
unique_objs.insert(obj);
}
}
if (auto prop = dynamic_cast<App::PropertyLinkSubList*>(prim->getPropertyByName("Sections"))) {
for (App::DocumentObject* obj : prop->getValues()) {
unique_objs.insert(obj);
}
}
if (auto prop = dynamic_cast<App::PropertyLinkSub*>(prim->getPropertyByName("ReferenceAxis"))) {
App::DocumentObject* axis = prop->getValue();
if (axis && !axis->isDerivedFrom<App::DatumElement>()){
unique_objs.insert(axis);
}
}
if (auto prop = dynamic_cast<App::PropertyLinkSub*>(prim->getPropertyByName("Spine"))) {
App::DocumentObject* axis = prop->getValue();
if (axis && !axis->isDerivedFrom<App::DatumElement>()){
unique_objs.insert(axis);
}
}
if (auto prop = dynamic_cast<App::PropertyLinkSub*>(prim->getPropertyByName("AuxiliarySpine"))) {
App::DocumentObject* axis = prop->getValue();
if (axis && !axis->isDerivedFrom<App::DatumElement>()){
unique_objs.insert(axis);
}
}
}
}
std::vector<App::DocumentObject*> result;
result.reserve(unique_objs.size());
result.insert(result.begin(), unique_objs.begin(), unique_objs.end());
return result;
}
void relinkToOrigin(App::DocumentObject* feat, PartDesign::Body* targetbody)
{
if (feat->hasExtension(Part::AttachExtension::getExtensionClassTypeId())) {
auto attachable = feat->getExtensionByType<Part::AttachExtension>();
App::DocumentObject* support = attachable->AttachmentSupport.getValue();
if (support && support->isDerivedFrom<App::DatumElement>()) {
auto originfeat = static_cast<App::DatumElement*>(support);
App::DatumElement* targetOriginFeature = targetbody->getOrigin()->getDatumElement(originfeat->Role.getValue());
if (targetOriginFeature) {
attachable->AttachmentSupport.setValue(static_cast<App::DocumentObject*>(targetOriginFeature), "");
}
}
}
else if (feat->isDerivedFrom<PartDesign::ProfileBased>()) {
auto prim = static_cast<PartDesign::ProfileBased*>(feat);
if (auto prop = static_cast<App::PropertyLinkSub*>(prim->getPropertyByName("ReferenceAxis"))) {
App::DocumentObject* axis = prop->getValue();
if (axis && axis->isDerivedFrom<App::DatumElement>()){
auto originfeat = static_cast<App::DatumElement*>(axis);
App::DatumElement* targetOriginFeature = targetbody->getOrigin()->getDatumElement(originfeat->Role.getValue());
if (targetOriginFeature) {
prop->setValue(static_cast<App::DocumentObject*>(targetOriginFeature), std::vector<std::string>(0));
}
}
}
}
}
} /* PartDesignGui */