/*************************************************************************** * Copyright (C) 2015 Alexander Golubev (Fat-Zer) * * * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #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(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() == 1; if (assertModern) { activeBody = activeView->getActiveObject(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(PDBODYKEY, topParent, subname); } return dynamic_cast(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) { Base::Reference hGrp = App::GetApplication().GetUserParameter().GetGroup( "BaseApp/Preferences/Mod/PartDesign" ); bool allowCompound = hGrp->GetBool("AllowCompoundDefault", true); // 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() ); Gui::Command::doCommand( Gui::Command::Doc, "App.getDocument('%s').getObject('%s').AllowCompound = %s", doc->getName(), bodyName.c_str(), allowCompound ? "True" : "False" ); auto body = dynamic_cast(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(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()) { 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 properties; obj->getPropertyList(properties); for (auto prop : properties) { if (prop->isDerivedFrom(App::PropertyLink::getClassTypeId())) { if (static_cast(prop)->getValue() == feature) { return true; } } else if (prop->isDerivedFrom(App::PropertyLinkSub::getClassTypeId())) { if (static_cast(prop)->getValue() == feature) { return true; } } else if (prop->isDerivedFrom(App::PropertyLinkList::getClassTypeId())) { auto values = static_cast(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(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 properties; obj->getPropertyList(properties); for (auto prop : properties) { std::string valueStr; if (prop->isDerivedFrom(App::PropertyLink::getClassTypeId())) { App::PropertyLink* propLink = static_cast(prop); if (propLink->getValue() != feature) { continue; } valueStr = Gui::Command::getObjectCmd(body); } else if (prop->isDerivedFrom(App::PropertyLinkSub::getClassTypeId())) { App::PropertyLinkSub* propLink = static_cast(prop); if (propLink->getValue() != feature) { continue; } valueStr = buildLinkSubPythonStr(body, propLink->getSubValues()); } else if (prop->isDerivedFrom(App::PropertyLinkList::getClassTypeId())) { App::PropertyLinkList* propLink = static_cast(prop); std::vector 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(prop); std::vector 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(feat)) { App::DocumentObject* bf = prim->BaseFeature.getValue(); if (bf) { return false; } } if (auto prim = dynamic_cast(feat)) { auto sk = prim->getVerifiedSketch(true); if (!isFeatureMovable(sk)) { return false; } if (auto prop = dynamic_cast(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(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(prim->getPropertyByName("ReferenceAxis"))) { App::DocumentObject* axis = prop->getValue(); if (axis && !isFeatureMovable(axis)) { return false; } } if (auto prop = dynamic_cast(prim->getPropertyByName("Spine"))) { App::DocumentObject* spine = prop->getValue(); if (spine && !isFeatureMovable(spine)) { return false; } } if (auto prop = dynamic_cast(prim->getPropertyByName("AuxiliarySpine"))) { App::DocumentObject* auxSpine = prop->getValue(); if (auxSpine && !isFeatureMovable(auxSpine)) { return false; } } } if (feat->hasExtension(Part::AttachExtension::getExtensionClassTypeId())) { auto attachable = feat->getExtensionByType(); App::DocumentObject* support = attachable->AttachmentSupport.getValue(); if (support && !support->isDerivedFrom()) { return false; } } return true; } std::vector collectMovableDependencies(std::vector& features) { std::set unique_objs; for (auto const& feat : features) { // Get sketches and datums from profile based features if (auto prim = dynamic_cast(feat)) { Part::Part2DObject* sk = prim->getVerifiedSketch(true); if (sk) { unique_objs.insert(sk); } if (auto prop = dynamic_cast(prim->getPropertyByName("Sections"))) { for (App::DocumentObject* obj : prop->getValues()) { unique_objs.insert(obj); } } if (auto prop = dynamic_cast(prim->getPropertyByName("Sections"))) { for (App::DocumentObject* obj : prop->getValues()) { unique_objs.insert(obj); } } if (auto prop = dynamic_cast(prim->getPropertyByName("ReferenceAxis"))) { App::DocumentObject* axis = prop->getValue(); if (axis && !axis->isDerivedFrom()) { unique_objs.insert(axis); } } if (auto prop = dynamic_cast(prim->getPropertyByName("Spine"))) { App::DocumentObject* axis = prop->getValue(); if (axis && !axis->isDerivedFrom()) { unique_objs.insert(axis); } } if (auto prop = dynamic_cast(prim->getPropertyByName("AuxiliarySpine"))) { App::DocumentObject* axis = prop->getValue(); if (axis && !axis->isDerivedFrom()) { unique_objs.insert(axis); } } } } std::vector 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(); App::DocumentObject* support = attachable->AttachmentSupport.getValue(); if (support && support->isDerivedFrom()) { auto originfeat = static_cast(support); App::DatumElement* targetOriginFeature = targetbody->getOrigin()->getDatumElement( originfeat->Role.getValue() ); if (targetOriginFeature) { attachable->AttachmentSupport.setValue( static_cast(targetOriginFeature), "" ); } } } else if (feat->isDerivedFrom()) { auto prim = static_cast(feat); if (auto prop = static_cast(prim->getPropertyByName("ReferenceAxis"))) { App::DocumentObject* axis = prop->getValue(); if (axis && axis->isDerivedFrom()) { auto originfeat = static_cast(axis); App::DatumElement* targetOriginFeature = targetbody->getOrigin()->getDatumElement( originfeat->Role.getValue() ); if (targetOriginFeature) { prop->setValue( static_cast(targetOriginFeature), std::vector(0) ); } } } } } } // namespace PartDesignGui