/****************************************************************************** * Copyright (c) 2012 Jan Rheinländer * * * * * * 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 #include #include #include #include #include #include #include "ui_TaskFeaturePick.h" #include "TaskFeaturePick.h" #include "Utils.h" #include using namespace PartDesignGui; using namespace Attacher; // TODO Do ve should snap here to App:Part or GeoFeatureGroup/DocumentObjectGroup ? (2015-09-04, // Fat-Zer) const QString TaskFeaturePick::getFeatureStatusString(const featureStatus st) { switch (st) { case validFeature: return tr("Valid"); case invalidShape: return tr("Invalid shape"); case noWire: return tr("No wire in sketch"); case isUsed: return tr("Sketch already used by other feature"); case otherBody: return tr("Belongs to another body"); case otherPart: return tr("Belongs to another part"); case notInBody: return tr("Doesn't belong to any body"); case basePlane: return tr("Base plane"); case afterTip: return tr("Feature is located after the tip of the body"); } return QString(); } TaskFeaturePick::TaskFeaturePick( std::vector& objects, const std::vector& status, bool singleFeatureSelect, QWidget* parent ) : TaskBox(Gui::BitmapFactory().pixmap("edit-select-all"), tr("Select Attachment"), true, parent) , ui(new Ui_TaskFeaturePick) , doSelection(false) { proxy = new QWidget(this); ui->setupUi(proxy); // clang-format off connect(ui->checkUsed, &QCheckBox::toggled, this, &TaskFeaturePick::onUpdate); connect(ui->checkOtherBody, &QCheckBox::toggled, this, &TaskFeaturePick::onUpdate); connect(ui->checkOtherPart, &QCheckBox::toggled, this, &TaskFeaturePick::onUpdate); connect(ui->radioIndependent, &QRadioButton::toggled, this, &TaskFeaturePick::onUpdate); connect(ui->radioDependent, &QRadioButton::toggled, this, &TaskFeaturePick::onUpdate); connect(ui->radioXRef, &QRadioButton::toggled, this, &TaskFeaturePick::onUpdate); connect(ui->listWidget, &QListWidget::itemSelectionChanged, this, &TaskFeaturePick::onItemSelectionChanged); connect(ui->listWidget, &QListWidget::itemDoubleClicked, this, &TaskFeaturePick::onDoubleClick); // clang-format on if (!singleFeatureSelect) { ui->listWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); } // NOTE: generally there shouldn't be more then one origin std::map originVisStatus; auto statusIt = status.cbegin(); auto objIt = objects.begin(); assert(status.size() == objects.size()); bool attached = false; for (; statusIt != status.end(); ++statusIt, ++objIt) { QListWidgetItem* item = new QListWidgetItem(QStringLiteral("%1 (%2)").arg( QString::fromUtf8((*objIt)->Label.getValue()), getFeatureStatusString(*statusIt) )); item->setData(Qt::UserRole, QString::fromLatin1((*objIt)->getNameInDocument())); ui->listWidget->addItem(item); App::Document* pDoc = (*objIt)->getDocument(); documentName = pDoc->getName(); if (!attached) { attached = true; attachDocument(Gui::Application::Instance->getDocument(pDoc)); } // check if we need to set any origin in temporary visibility mode auto* datum = dynamic_cast(*objIt); if ((*statusIt == validFeature || *statusIt == basePlane) && datum) { auto* origin = dynamic_cast(datum->getLCS()); if (origin) { if ((*objIt)->isDerivedFrom()) { originVisStatus[origin].setFlag(Gui::DatumElement::Planes, true); } else if ((*objIt)->isDerivedFrom()) { originVisStatus[origin].setFlag(Gui::DatumElement::Axes, true); } } } } // Setup the origin's temporary visibility for (const auto& originPair : originVisStatus) { const auto& origin = originPair.first; auto* vpo = static_cast( Gui::Application::Instance->getViewProvider(origin) ); if (vpo) { vpo->setTemporaryVisibility(originVisStatus[origin]); vpo->setTemporaryScale(Gui::ViewParams::instance()->getDatumTemporaryScaleFactor()); vpo->setPlaneLabelVisibility(true); origins.push_back(vpo); } } // TODO may be update origin API to show only some objects (2015-08-31, Fat-Zer) groupLayout()->addWidget(proxy); statuses = status; updateList(); } TaskFeaturePick::~TaskFeaturePick() { for (Gui::ViewProviderCoordinateSystem* vpo : origins) { vpo->resetTemporaryVisibility(); vpo->resetTemporarySize(); vpo->setPlaneLabelVisibility(false); } } void TaskFeaturePick::updateList() { int index = 0; for (auto status : statuses) { QListWidgetItem* item = ui->listWidget->item(index); switch (status) { case validFeature: item->setHidden(false); break; case invalidShape: item->setHidden(true); break; case isUsed: item->setHidden(!ui->checkUsed->isChecked()); break; case noWire: item->setHidden(true); break; case otherBody: item->setHidden(!ui->checkOtherBody->isChecked()); break; case otherPart: item->setHidden(!ui->checkOtherPart->isChecked()); break; case notInBody: item->setHidden(!ui->checkOtherPart->isChecked()); break; case basePlane: item->setHidden(false); break; case afterTip: item->setHidden(true); break; } index++; } } void TaskFeaturePick::onUpdate(bool) { bool enable = false; if (ui->checkOtherBody->isChecked() || ui->checkOtherPart->isChecked()) { enable = true; } ui->radioDependent->setEnabled(enable); ui->radioIndependent->setEnabled(enable); ui->radioXRef->setEnabled(enable); updateList(); } std::vector TaskFeaturePick::getFeatures() { features.clear(); QListIterator i(ui->listWidget->selectedItems()); while (i.hasNext()) { auto item = i.next(); if (item->isHidden()) { continue; } QString t = item->data(Qt::UserRole).toString(); features.push_back(t); } std::vector result; for (const auto& feature : features) { result.push_back( App::GetApplication().getDocument(documentName.c_str())->getObject(feature.toLatin1().data()) ); } return result; } std::vector TaskFeaturePick::buildFeatures() { int index = 0; std::vector result; try { auto activeBody = PartDesignGui::getBody(false); if (!activeBody) { return result; } auto activePart = PartDesignGui::getPartFor(activeBody, false); for (auto status : statuses) { QListWidgetItem* item = ui->listWidget->item(index); if (item->isSelected() && !item->isHidden()) { QString t = item->data(Qt::UserRole).toString(); auto obj = App::GetApplication() .getDocument(documentName.c_str()) ->getObject(t.toLatin1().data()); // build the dependent copy or reference if wanted by the user if (status == otherBody || status == otherPart || status == notInBody) { if (!ui->radioXRef->isChecked()) { auto copy = makeCopy(obj, "", ui->radioIndependent->isChecked()); if (status == otherBody) { activeBody->addObject(copy); } else if (status == otherPart) { auto oBody = PartDesignGui::getBodyFor(obj, false); if (!oBody) { activePart->addObject(copy); } else { activeBody->addObject(copy); } } else if (status == notInBody) { activeBody->addObject(copy); // doesn't supposed to get here anything but sketch but to be on the // safe side better to check if (copy->isDerivedFrom()) { Sketcher::SketchObject* sketch = static_cast(copy); PartDesignGui::fixSketchSupport(sketch); } } result.push_back(copy); } else { result.push_back(obj); } } else { result.push_back(obj); } } index++; } } catch (const Base::Exception& e) { e.reportException(); } catch (Py::Exception& e) { // reported by code analyzers e.clear(); Base::Console().warning("Unexpected PyCXX exception\n"); } catch (const boost::exception&) { // reported by code analyzers Base::Console().warning("Unexpected boost exception\n"); } return result; } App::DocumentObject* TaskFeaturePick::makeCopy(App::DocumentObject* obj, std::string sub, bool independent) { App::DocumentObject* copy = nullptr; // Check for null to avoid segfault if (!obj) { return copy; } if (independent && (obj->isDerivedFrom() || obj->isDerivedFrom())) { // we do know that the created instance is a document object, as obj is one. But we do not // know which exact type auto* doc = App::GetApplication().getActiveDocument(); const auto name = fmt::format("Copy{}", obj->getNameInDocument()); copy = doc->addObject(obj->getTypeId().getName(), name.c_str()); // copy over all properties std::vector props; std::vector cprops; obj->getPropertyList(props); copy->getPropertyList(cprops); auto it = cprops.begin(); for (App::Property* prop : props) { // independent copies don't have links and are not attached if (independent && (prop->isDerivedFrom() || prop->isDerivedFrom() || prop->isDerivedFrom() || prop->isDerivedFrom() || (prop->getGroup() && strcmp(prop->getGroup(), "Attachment") == 0))) { ++it; continue; } App::Property* cprop = *it++; if (prop->getName() && strcmp(prop->getName(), "Label") == 0) { static_cast(cprop)->setValue(name.c_str()); continue; } cprop->Paste(*prop); // we are a independent copy, therefore no external geometry was copied. WE therefore // can delete all constraints if (auto* sketchObj = freecad_cast(obj)) { sketchObj->delConstraintsToExternal(); } } } else { const std::string name = (!independent ? std::string("Reference") : std::string("Copy")) + obj->getNameInDocument(); const std::string entity = sub; Part::PropertyPartShape* shapeProp = nullptr; // TODO Replace it with commands (2015-09-11, Fat-Zer) if (obj->isDerivedFrom()) { auto* doc = App::GetApplication().getActiveDocument(); copy = doc->addObject(name.c_str()); // we need to reference the individual datums and make again datums. This is important // as datum adjust their size dependent on the part size, hence simply copying the shape // is not enough long int mode = mmDeactivated; Part::Datum* datumCopy = static_cast(copy); if (obj->is()) { mode = mm0Vertex; } else if (obj->is()) { mode = mm1TwoPoints; } else if (obj->is()) { mode = mmFlatFace; } else { return copy; } // TODO Recheck this. This looks strange in case of independent copy (2015-10-31, // Fat-Zer) if (!independent) { datumCopy->AttachmentSupport.setValue(obj, entity.c_str()); datumCopy->MapMode.setValue(mode); } else if (!entity.empty()) { datumCopy->Shape.setValue( static_cast(obj)->Shape.getShape().getSubShape(entity.c_str()) ); } else { datumCopy->Shape.setValue(static_cast(obj)->Shape.getValue()); } } else if (obj->is() || obj->isDerivedFrom()) { auto* doc = App::GetApplication().getActiveDocument(); auto* shapeBinderObj = doc->addObject(name.c_str()); if (!independent) { shapeBinderObj->Support.setValue(obj, entity.c_str()); } else { shapeProp = &shapeBinderObj->Shape; } copy = shapeBinderObj; } else if (obj->isDerivedFrom() || obj->isDerivedFrom()) { auto* doc = App::GetApplication().getActiveDocument(); auto* shapeBinderObj = doc->addObject(name.c_str()); if (!independent) { shapeBinderObj->Support.setValue(obj, entity.c_str()); } else { std::vector subvalues; subvalues.push_back(entity); Part::TopoShape shape = PartDesign::ShapeBinder::buildShapeFromReferences(shapeBinderObj, subvalues); shapeBinderObj->Shape.setValue(shape); } copy = shapeBinderObj; } if (independent && shapeProp) { auto* featureObj = static_cast(obj); shapeProp->setValue( entity.empty() ? featureObj->Shape.getValue() : featureObj->Shape.getShape().getSubShape(entity.c_str()) ); } } return copy; } bool TaskFeaturePick::isSingleSelectionEnabled() const { ParameterGrp::handle hGrp = App::GetApplication() .GetUserParameter() .GetGroup("BaseApp") ->GetGroup("Preferences") ->GetGroup("Selection"); return hGrp->GetBool("singleClickFeatureSelect", true); } void TaskFeaturePick::onSelectionChanged(const Gui::SelectionChanges& msg) { if (doSelection) { return; } doSelection = true; ui->listWidget->clearSelection(); for (Gui::SelectionSingleton::SelObj obj : Gui::Selection().getSelection()) { for (int row = 0; row < ui->listWidget->count(); row++) { QListWidgetItem* item = ui->listWidget->item(row); QString t = item->data(Qt::UserRole).toString(); if (t.compare(QString::fromLatin1(obj.FeatName)) == 0) { item->setSelected(true); if (msg.Type == Gui::SelectionChanges::AddSelection) { if (isSingleSelectionEnabled()) { QMetaObject::invokeMethod( qobject_cast(&Gui::Control()), "accept", Qt::QueuedConnection ); } } } } } doSelection = false; } void TaskFeaturePick::onItemSelectionChanged() { if (doSelection) { return; } doSelection = true; ui->listWidget->blockSignals(true); Gui::Selection().clearSelection(); for (int row = 0; row < ui->listWidget->count(); row++) { QListWidgetItem* item = ui->listWidget->item(row); QString t = item->data(Qt::UserRole).toString(); if (item->isSelected()) { Gui::Selection().addSelection(documentName.c_str(), t.toLatin1()); } } ui->listWidget->blockSignals(false); doSelection = false; } void TaskFeaturePick::onDoubleClick(QListWidgetItem* item) { if (doSelection) { return; } doSelection = true; QString t = item->data(Qt::UserRole).toString(); Gui::Selection().addSelection(documentName.c_str(), t.toLatin1()); doSelection = false; QMetaObject::invokeMethod( qobject_cast(&Gui::Control()), "accept", Qt::QueuedConnection ); } void TaskFeaturePick::slotDeletedObject(const Gui::ViewProviderDocumentObject& Obj) { if (const auto it = std::ranges::find(origins, &Obj); it != origins.end()) { origins.erase(it); } } void TaskFeaturePick::slotUndoDocument(const Gui::Document&) { if (origins.empty()) { QTimer::singleShot(100, &Gui::Control(), &Gui::ControlSingleton::closeDialog); } } void TaskFeaturePick::slotDeleteDocument(const Gui::Document&) { origins.clear(); QTimer::singleShot(100, &Gui::Control(), &Gui::ControlSingleton::closeDialog); } void TaskFeaturePick::showExternal(bool val) { ui->checkOtherBody->setChecked(val); ui->checkOtherPart->setChecked(val); updateList(); } //************************************************************************** //************************************************************************** // TaskDialog //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TaskDlgFeaturePick::TaskDlgFeaturePick( std::vector& objects, const std::vector& status, std::function)> afunc, std::function)> wfunc, bool singleFeatureSelect, std::function abortfunc /* = NULL */ ) : TaskDialog() , accepted(false) { pick = new TaskFeaturePick(objects, status, singleFeatureSelect); Content.push_back(pick); acceptFunction = afunc; workFunction = wfunc; abortFunction = abortfunc; } TaskDlgFeaturePick::~TaskDlgFeaturePick() { // do the work now as before in accept() the dialog is still open, hence the work // function could not open another dialog if (accepted) { try { workFunction(pick->buildFeatures()); } catch (...) { } } else if (abortFunction) { // Get rid of the TaskFeaturePick before the TaskDialog dtor does. The // TaskFeaturePick holds pointers to things (ie any implicitly created // Body objects) that might be modified/removed by abortFunction. for (auto it : Content) { delete it; } Content.clear(); try { abortFunction(); } catch (...) { } } } //==== calls from the TaskView =============================================================== void TaskDlgFeaturePick::open() {} void TaskDlgFeaturePick::clicked(int) {} bool TaskDlgFeaturePick::accept() { accepted = acceptFunction(pick->getFeatures()); return accepted; } bool TaskDlgFeaturePick::reject() { accepted = false; return true; } void TaskDlgFeaturePick::showExternal(bool val) { pick->showExternal(val); } #include "moc_TaskFeaturePick.cpp"