diff --git a/src/Mod/Assembly/App/AppAssembly.cpp b/src/Mod/Assembly/App/AppAssembly.cpp index 2f5eacb5ee..e9152d4dae 100644 --- a/src/Mod/Assembly/App/AppAssembly.cpp +++ b/src/Mod/Assembly/App/AppAssembly.cpp @@ -28,6 +28,8 @@ #include #include "AssemblyObject.h" +#include "BomObject.h" +#include "BomGroup.h" #include "JointGroup.h" #include "ViewGroup.h" @@ -43,6 +45,7 @@ PyMOD_INIT_FUNC(AssemblyApp) // load dependent module try { Base::Interpreter().runString("import Part"); + Base::Interpreter().runString("import Spreadsheet"); } catch (const Base::Exception& e) { PyErr_SetString(PyExc_ImportError, e.what()); @@ -58,6 +61,9 @@ PyMOD_INIT_FUNC(AssemblyApp) // This function is responsible for adding inherited slots from a type's base class. Assembly::AssemblyObject ::init(); + Assembly::BomObject ::init(); + + Assembly::BomGroup ::init(); Assembly::JointGroup ::init(); Assembly::ViewGroup ::init(); diff --git a/src/Mod/Assembly/App/BomGroup.cpp b/src/Mod/Assembly/App/BomGroup.cpp new file mode 100644 index 0000000000..7754c39f6d --- /dev/null +++ b/src/Mod/Assembly/App/BomGroup.cpp @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2023 Ondsel * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +#endif + +#include +#include +#include +#include +#include +#include + +#include "BomGroup.h" +#include "BomGroupPy.h" + +using namespace Assembly; + + +PROPERTY_SOURCE(Assembly::BomGroup, App::DocumentObjectGroup) + +BomGroup::BomGroup() +{} + +BomGroup::~BomGroup() = default; + +PyObject* BomGroup::getPyObject() +{ + if (PythonObject.is(Py::_None())) { + // ref counter is set to 1 + PythonObject = Py::Object(new BomGroupPy(this), true); + } + return Py::new_reference_to(PythonObject); +} diff --git a/src/Mod/Assembly/App/BomGroup.h b/src/Mod/Assembly/App/BomGroup.h new file mode 100644 index 0000000000..0eb492129e --- /dev/null +++ b/src/Mod/Assembly/App/BomGroup.h @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2023 Ondsel * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + + +#ifndef ASSEMBLY_BomGroup_H +#define ASSEMBLY_BomGroup_H + +#include + +#include +#include + + +namespace Assembly +{ + +class AssemblyExport BomGroup: public App::DocumentObjectGroup +{ + PROPERTY_HEADER_WITH_OVERRIDE(Assembly::BomGroup); + +public: + BomGroup(); + ~BomGroup() override; + + PyObject* getPyObject() override; + + /// returns the type name of the ViewProvider + const char* getViewProviderName() const override + { + return "AssemblyGui::ViewProviderBomGroup"; + } +}; + + +} // namespace Assembly + + +#endif // ASSEMBLY_BomGroup_H diff --git a/src/Mod/Assembly/App/BomGroupPy.xml b/src/Mod/Assembly/App/BomGroupPy.xml new file mode 100644 index 0000000000..2a5c48abf6 --- /dev/null +++ b/src/Mod/Assembly/App/BomGroupPy.xml @@ -0,0 +1,19 @@ + + + + + + This class is a group subclass for boms. + + + + + diff --git a/src/Mod/Assembly/App/BomGroupPyImp.cpp b/src/Mod/Assembly/App/BomGroupPyImp.cpp new file mode 100644 index 0000000000..a73a733cf7 --- /dev/null +++ b/src/Mod/Assembly/App/BomGroupPyImp.cpp @@ -0,0 +1,46 @@ +/*************************************************************************** + * Copyright (c) 2014 Jürgen Riegel * + * * + * 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 "PreCompiled.h" + +// inclusion of the generated files (generated out of BomGroup.xml) +#include "BomGroupPy.h" +#include "BomGroupPy.cpp" + +using namespace Assembly; + +// returns a string which represents the object e.g. when printed in python +std::string BomGroupPy::representation() const +{ + return {""}; +} + +PyObject* BomGroupPy::getCustomAttributes(const char* /*attr*/) const +{ + return nullptr; +} + +int BomGroupPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) +{ + return 0; +} diff --git a/src/Mod/Assembly/App/BomObject.cpp b/src/Mod/Assembly/App/BomObject.cpp new file mode 100644 index 0000000000..4fe002914f --- /dev/null +++ b/src/Mod/Assembly/App/BomObject.cpp @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2023 Ondsel * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +#include "AssemblyObject.h" +#include "BomGroup.h" +#include "JointGroup.h" +#include "ViewGroup.h" +#include "BomObject.h" +#include "BomObjectPy.h" + + +using namespace Assembly; + +// ================================ Assembly Object ============================ + +PROPERTY_SOURCE(Assembly::BomObject, Spreadsheet::Sheet) + +BomObject::BomObject() + : Spreadsheet::Sheet() +{ + ADD_PROPERTY_TYPE(columnsNames, + ("Index"), + "Bom", + (App::PropertyType)(App::Prop_None), + "List of the columns of the Bill of Materials."); + + ADD_PROPERTY_TYPE(detailSubAssemblies, + (true), + "Bom", + (App::PropertyType)(App::Prop_None), + "Detail sub-assemblies components."); + + ADD_PROPERTY_TYPE(detailParts, + (true), + "Bom", + (App::PropertyType)(App::Prop_None), + "Detail Parts sub-components."); + + ADD_PROPERTY_TYPE( + onlyParts, + (false), + "Bom", + (App::PropertyType)(App::Prop_None), + "Only Part containers will be added. Solids like PartDesign Bodies will be ignored."); +} +BomObject::~BomObject() = default; + +PyObject* BomObject::getPyObject() +{ + if (PythonObject.is(Py::_None())) { + // ref counter is set to 1 + PythonObject = Py::Object(new BomObjectPy(this), true); + } + return Py::new_reference_to(PythonObject); +} + +App::DocumentObjectExecReturn* BomObject::execute() +{ + generateBOM(); + + return Spreadsheet::Sheet::execute(); +} + +void BomObject::saveCustomColumnData() +{ + // This function saves data that is not automatically generated. + dataElements.clear(); + std::tuple res = getUsedRange(); + int maxRow = std::get<1>(res).row(); + int nameColIndex = getColumnIndex("Name"); + + for (int row = 1; row <= maxRow; ++row) { + + size_t col = 0; + // Here we do not iterate columnName : columnsNames.getValues() because they may have + // changed + for (size_t i = 0; i < columnsNames.getValues().size(); ++i) { + std::string columnName = getText(0, i); + if (columnName != "Index" && columnName != "Name" && columnName != "Quantity" + && columnName != "File Name") { + // Base::Console().Warning("row col %d %d\n", row, col); + // save custom data if any. + std::string text = getText(row, col); + if (text != "") { + std::string objName = getText(row, nameColIndex); + BomDataElement el(objName, columnName, text); + dataElements.push_back(el); + } + } + + ++col; + } + } +} + +void BomObject::generateBOM() +{ + saveCustomColumnData(); + clearAll(); + size_t row = 0; + size_t col = 0; + + // Populate headers + for (auto& columnName : columnsNames.getValues()) { + setCell(App::CellAddress(row, col), columnName.c_str()); + ++col; + } + ++row; + + auto* assembly = getAssembly(); + if (assembly) { + addObjectChildrenToBom(assembly->getOutList(), row, ""); + } + else { + addObjectChildrenToBom(getDocument()->getRootObjectsIgnoreLinks(), row, ""); + } +} + +void BomObject::addObjectChildrenToBom(std::vector objs, + size_t& row, + std::string index) +{ + int nameColIndex = getColumnIndex("Name"); + int quantityColIndex = getColumnIndex("Quantity"); + bool hasQuantityCol = hasQuantityColumn(); + + int siblingsInitialRow = row; + + if (index != "") { + index = index + "."; + } + + size_t sub_i = 1; + + for (auto* child : objs) { + if (child->isDerivedFrom()) { + child = static_cast(child)->getLinkedObject(); + } + + if (child->isDerivedFrom() || child->isDerivedFrom() + || child->isDerivedFrom()) { + continue; + } + + if (hasQuantityCol) { + // Check if the object is not already in (case of links). And if so just increment. + // Note: an object can be used in several parts. In which case we do no want to blindly + // increment. + bool found = false; + for (size_t i = siblingsInitialRow; i <= row; ++i) { + std::string childName = child->Label.getValue(); + + if (childName == getText(i, nameColIndex) && childName != "") { + int qty = std::stoi(getText(i, quantityColIndex)) + 1; + setCell(App::CellAddress(i, quantityColIndex), std::to_string(qty).c_str()); + found = true; + break; + } + } + if (found) { + continue; + } + } + + if (child->isDerivedFrom() + || child->isDerivedFrom() || child->isDerivedFrom() + || (child->isDerivedFrom() && !onlyParts.getValue())) { + + std::string sub_index = index + std::to_string(sub_i); + ++sub_i; + + addObjectToBom(child, row, sub_index); + ++row; + + if (child->isDerivedFrom() + || (child->isDerivedFrom() && detailSubAssemblies.getValue()) + || (child->isDerivedFrom() && detailParts.getValue())) { + addObjectChildrenToBom(child->getOutList(), row, sub_index); + } + } + } +} + +void BomObject::addObjectToBom(App::DocumentObject* obj, size_t row, std::string index) +{ + size_t col = 0; + for (auto& columnName : columnsNames.getValues()) { + if (columnName == "Index") { + setCell(App::CellAddress(row, col), index.c_str()); + } + else if (columnName == "Name") { + setCell(App::CellAddress(row, col), obj->Label.getValue()); + } + else if (columnName == "File Name") { + setCell(App::CellAddress(row, col), obj->getDocument()->getFileName()); + } + else if (columnName == "Quantity") { + setCell(App::CellAddress(row, col), std::to_string(1).c_str()); + } + else { + // load custom data if any. + for (auto& el : dataElements) { + if (el.objName == obj->Label.getValue() && el.columnName == columnName) { + setCell(App::CellAddress(row, col), el.value.c_str()); + break; + } + } + } + ++col; + } +} + +AssemblyObject* BomObject::getAssembly() +{ + for (auto& obj : getInList()) { + if (obj->isDerivedFrom()) { + return static_cast(obj); + } + } + return nullptr; +} + +bool BomObject::hasQuantityColumn() +{ + for (auto& columnName : columnsNames.getValues()) { + if (columnName == "Quantity") { + return true; + } + } + return false; +} + +std::string Assembly::BomObject::getText(size_t row, size_t col) +{ + Spreadsheet::Cell* cell = getCell(App::CellAddress(row, col)); + std::string cellName; + if (cell) { + cell->getStringContent(cellName); + + // getStringContent is addind a ' before the string for whatever reason. + if (!cellName.empty() && cellName.front() == '\'') { + cellName.erase(0, 1); // Remove the first character if it's a ' + } + } + + return cellName; +} + +int BomObject::getColumnIndex(std::string name) +{ + int col = 0; + for (auto& columnName : columnsNames.getValues()) { + if (columnName == name) { + return col; + } + ++col; + } + return -1; +} diff --git a/src/Mod/Assembly/App/BomObject.h b/src/Mod/Assembly/App/BomObject.h new file mode 100644 index 0000000000..0dadccfe1e --- /dev/null +++ b/src/Mod/Assembly/App/BomObject.h @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2023 Ondsel * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + + +#ifndef ASSEMBLY_BomObject_H +#define ASSEMBLY_BomObject_H + +#include + +#include + +#include +#include + +namespace App +{ +class DocumentObject; +} + +namespace Assembly +{ + +class AssemblyObject; + +class BomDataElement +{ +public: + BomDataElement(std::string objName, std::string columnName, std::string value) + : objName(objName) + , columnName(columnName) + , value(value) + {} + ~BomDataElement() + {} + + std::string objName; + std::string columnName; + std::string value; +}; + +class AssemblyExport BomObject: public Spreadsheet::Sheet +{ + PROPERTY_HEADER_WITH_OVERRIDE(Assembly::BomObject); + +public: + BomObject(); + ~BomObject() override; + + PyObject* getPyObject() override; + + const char* getViewProviderName() const override + { + return "AssemblyGui::ViewProviderBom"; + } + + App::DocumentObjectExecReturn* execute() override; + + void generateBOM(); + void addObjectToBom(App::DocumentObject* obj, size_t row, std::string index); + void + addObjectChildrenToBom(std::vector objs, size_t& row, std::string index); + void saveCustomColumnData(); + + AssemblyObject* getAssembly(); + + bool hasQuantityColumn(); + int getColumnIndex(std::string name); + std::string getText(size_t row, size_t col); + + App::PropertyStringList columnsNames; + App::PropertyBool detailSubAssemblies; + App::PropertyBool detailParts; + App::PropertyBool onlyParts; + + std::vector dataElements; +}; + + +} // namespace Assembly + + +#endif // ASSEMBLY_BomObject_H diff --git a/src/Mod/Assembly/App/BomObjectPy.xml b/src/Mod/Assembly/App/BomObjectPy.xml new file mode 100644 index 0000000000..b329fcc984 --- /dev/null +++ b/src/Mod/Assembly/App/BomObjectPy.xml @@ -0,0 +1,19 @@ + + + + + + This class is the BOM object of assemblies, it derives from Spreadsheet::Sheet. + + + + + diff --git a/src/Mod/Assembly/App/BomObjectPyImp.cpp b/src/Mod/Assembly/App/BomObjectPyImp.cpp new file mode 100644 index 0000000000..3df251f35e --- /dev/null +++ b/src/Mod/Assembly/App/BomObjectPyImp.cpp @@ -0,0 +1,46 @@ +/*************************************************************************** + * Copyright (c) 2024 Boyer Pierre-Louis * + * * + * 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 "PreCompiled.h" + +// inclusion of the generated files (generated out of BomObject.xml) +#include "BomObjectPy.h" +#include "BomObjectPy.cpp" + +using namespace Assembly; + +// returns a string which represents the object e.g. when printed in python +std::string BomObjectPy::representation() const +{ + return {""}; +} + +PyObject* BomObjectPy::getCustomAttributes(const char* /*attr*/) const +{ + return nullptr; +} + +int BomObjectPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) +{ + return 0; +} diff --git a/src/Mod/Assembly/App/CMakeLists.txt b/src/Mod/Assembly/App/CMakeLists.txt index abab22a43a..afbf1c1870 100644 --- a/src/Mod/Assembly/App/CMakeLists.txt +++ b/src/Mod/Assembly/App/CMakeLists.txt @@ -13,17 +13,24 @@ link_directories(${OCC_LIBRARY_DIR}) set(Assembly_LIBS Part PartDesign + Spreadsheet FreeCADApp OndselSolver ) generate_from_xml(AssemblyObjectPy) +generate_from_xml(BomObjectPy) +generate_from_xml(BomGroupPy) generate_from_xml(JointGroupPy) generate_from_xml(ViewGroupPy) SET(Python_SRCS AssemblyObjectPy.xml AssemblyObjectPyImp.cpp + BomObjectPy.xml + BomObjectPyImp.cpp + BomGroupPy.xml + BomGroupPyImp.cpp JointGroupPy.xml JointGroupPyImp.cpp ViewGroupPy.xml @@ -42,6 +49,10 @@ SOURCE_GROUP("Module" FILES ${Module_SRCS}) SET(Assembly_SRCS AssemblyObject.cpp AssemblyObject.h + BomObject.cpp + BomObject.h + BomGroup.cpp + BomGroup.h JointGroup.cpp JointGroup.h ViewGroup.cpp diff --git a/src/Mod/Assembly/CMakeLists.txt b/src/Mod/Assembly/CMakeLists.txt index ea597ccd43..65de263288 100644 --- a/src/Mod/Assembly/CMakeLists.txt +++ b/src/Mod/Assembly/CMakeLists.txt @@ -7,6 +7,7 @@ endif(BUILD_GUI) set(Assembly_Scripts Init.py CommandCreateAssembly.py + CommandCreateBom.py CommandInsertLink.py CommandSolveAssembly.py CommandCreateJoint.py diff --git a/src/Mod/Assembly/CommandCreateBom.py b/src/Mod/Assembly/CommandCreateBom.py new file mode 100644 index 0000000000..658603148c --- /dev/null +++ b/src/Mod/Assembly/CommandCreateBom.py @@ -0,0 +1,294 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# /************************************************************************** +# * +# Copyright (c) 2023 Ondsel * +# * +# This file is part of FreeCAD. * +# * +# FreeCAD is free software: you can redistribute it and/or modify it * +# under the terms of the GNU Lesser General Public License as * +# published by the Free Software Foundation, either version 2.1 of the * +# License, or (at your option) any later version. * +# * +# FreeCAD is distributed in the hope that it will be useful, but * +# WITHOUT ANY WARRANTY; without even the implied warranty of * +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * +# Lesser General Public License for more details. * +# * +# You should have received a copy of the GNU Lesser General Public * +# License along with FreeCAD. If not, see * +# . * +# * +# **************************************************************************/ + +import re +import os +import FreeCAD as App + +from PySide.QtCore import QT_TRANSLATE_NOOP + +if App.GuiUp: + import FreeCADGui as Gui + from PySide import QtCore, QtGui, QtWidgets, QtUiTools + from PySide.QtWidgets import QPushButton, QMenu + +import UtilsAssembly +import Preferences + +# translate = App.Qt.translate + +__title__ = "Assembly Command Create Bill of Materials" +__author__ = "Ondsel" +__url__ = "https://www.freecad.org" + +translate = App.Qt.translate + +TranslatedColumnNames = [ + translate("Assembly", "Index"), + translate("Assembly", "Name"), + translate("Assembly", "Description"), + translate("Assembly", "File Name"), + translate("Assembly", "Quantity"), +] + +ColumnNames = [ + "Index", + "Name", + "Description", + "File Name", + "Quantity", +] + + +class CommandCreateBom: + def __init__(self): + pass + + def GetResources(self): + return { + "Pixmap": "Assembly_BillOfMaterials", + "MenuText": QT_TRANSLATE_NOOP("Assembly_BillOfMaterials", "Create Bill of Materials"), + "Accel": "O", + "ToolTip": "

" + + QT_TRANSLATE_NOOP( + "Assembly_CreateView", + "Create a bill of materials of the current assembly. If an assembly is active, it will be a BOM of this assembly. Else it will be a BOM of the whole document.", + ) + + "

" + + QT_TRANSLATE_NOOP( + "Assembly_CreateView", + "The BOM object is a document object that stores the settings of your BOM. It is also a spreadsheet object so you can easily visualize the bom. If you don't need the BOM object to be saved as a document object, you can simply export and cancel the task.", + ) + + "

" + + QT_TRANSLATE_NOOP( + "Assembly_CreateView", + "The columns 'Index', 'Name', 'File Name' and 'Quantity' are automatically generated on recompute. The 'Description' and custom columns are not overwriten.", + ) + + "

", + "CmdType": "ForEdit", + } + + def IsActive(self): + return True + + def Activated(self): + self.panel = TaskAssemblyCreateBom() + Gui.Control.showDialog(self.panel) + + +######### Create Exploded View Task ########### +class TaskAssemblyCreateBom(QtCore.QObject): + def __init__(self, bomObj=None): + super().__init__() + + self.form = Gui.PySideUic.loadUi(":/panels/TaskAssemblyCreateBom.ui") + + # Set the QListWidget properties to support drag and drop + self.form.columnList.setEditTriggers( + QtWidgets.QAbstractItemView.DoubleClicked | QtWidgets.QAbstractItemView.EditKeyPressed + ) + self.form.columnList.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) + self.form.columnList.setDragEnabled(True) + self.form.columnList.setAcceptDrops(True) + self.form.columnList.setDropIndicatorShown(True) + self.form.columnList.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove) + + self.form.columnList.installEventFilter(self) + + self.form.btnAddColumn.clicked.connect(self.addColumn) + self.form.btnExport.clicked.connect(self.export) + + pref = Preferences.preferences() + + if bomObj: + App.setActiveTransaction("Edit Bill Of Materials") + + names = [] + for i in range(len(bomObj.columnsNames)): + text = bomObj.columnsNames[i] + if text in ColumnNames: + index = ColumnNames.index(text) + text = TranslatedColumnNames[index] + names.append(text) + self.form.columnList.addItems(names) + + self.bomObj = bomObj + self.form.CheckBox_onlyParts.setChecked(bomObj.onlyParts) + self.form.CheckBox_detailParts.setChecked(bomObj.detailParts) + self.form.CheckBox_detailSubAssemblies.setChecked(bomObj.detailSubAssemblies) + + else: + App.setActiveTransaction("Create Bill Of Materials") + self.form.columnList.addItems(TranslatedColumnNames) + + self.createBomObject() + self.form.CheckBox_onlyParts.setChecked(pref.GetBool("BOMOnlyParts", False)) + self.form.CheckBox_detailParts.setChecked(pref.GetBool("BOMDetailParts", True)) + self.form.CheckBox_detailSubAssemblies.setChecked( + pref.GetBool("BOMDetailSubAssemblies", True) + ) + + self.form.columnList.model().rowsMoved.connect(self.onItemsReordered) + self.form.columnList.itemChanged.connect(self.itemUpdated) + + self.form.CheckBox_onlyParts.stateChanged.connect(self.onIncludeSolids) + self.form.CheckBox_detailParts.stateChanged.connect(self.onDetailParts) + self.form.CheckBox_detailSubAssemblies.stateChanged.connect(self.onDetailSubAssemblies) + + self.updateColumnList() + + def accept(self): + self.deactivate() + App.closeActiveTransaction() + + self.bomObj.recompute() + + self.bomObj.ViewObject.showSheetMdi() + + return True + + def reject(self): + self.deactivate() + App.closeActiveTransaction(True) + return True + + def deactivate(self): + pref = Preferences.preferences() + pref.SetBool("BOMOnlyParts", self.form.CheckBox_onlyParts.isChecked()) + pref.SetBool("BOMDetailParts", self.form.CheckBox_detailParts.isChecked()) + pref.SetBool("BOMDetailSubAssemblies", self.form.CheckBox_detailSubAssemblies.isChecked()) + + if Gui.Control.activeDialog(): + Gui.Control.closeDialog() + + def onIncludeSolids(self, val): + self.bomObj.onlyParts = val + + def onDetailParts(self, val): + self.bomObj.detailParts = val + + def onDetailSubAssemblies(self, val): + self.bomObj.detailSubAssemblies = val + + def addColumn(self): + new_name = translate("Assembly", "Default") + if self.isNameDuplicate(new_name): + # Find a unique name + counter = 1 + while self.isNameDuplicate(f"{new_name}_{counter}"): + counter += 1 + new_name = f"{new_name}_{counter}" + + new_item = QtWidgets.QListWidgetItem(new_name) + new_item.setFlags(new_item.flags() | QtCore.Qt.ItemIsEditable) + self.form.columnList.addItem(new_item) + + # Ensure the new item is selected and starts editing + self.form.columnList.setCurrentItem(new_item) + self.form.columnList.editItem(new_item) + self.updateColumnList() + + def onItemsReordered(self, parent, start, end, destination, row): + self.updateColumnList() + + def updateColumnList(self): + if self.bomObj: + new_names = [] + for i in range(self.form.columnList.count()): + text = self.form.columnList.item(i).text() + if text in TranslatedColumnNames: + index = TranslatedColumnNames.index(text) + text = ColumnNames[index] + new_names.append(text) + self.bomObj.columnsNames = new_names + + def itemUpdated(self, item): + new_text = item.text() + item_row = self.form.columnList.row(item) + + # Check for duplicate names + duplicate_found = False + for i in range(self.form.columnList.count()): + if i != item_row and self.form.columnList.item(i).text() == new_text: + duplicate_found = True + break + + if duplicate_found: + QtWidgets.QMessageBox.warning( + self.form, + translate("Assembly", "Duplicate Name"), + translate("Assembly", "This name is already used. Please choose a different name."), + ) + + # Revert the change + old_text = ( + self.bomObj.columnsNames[item_row] + if self.bomObj and item_row < len(self.bomObj.columnsNames) + else "" + ) + item.setText(old_text) + else: + self.updateColumnList() + + def isNameDuplicate(self, name): + for i in range(self.form.columnList.count()): + if self.form.columnList.item(i).text() == name: + return True + return False + + def createBomObject(self): + assembly = UtilsAssembly.activeAssembly() + if assembly is not None: + bom_group = UtilsAssembly.getBomGroup(assembly) + self.bomObj = bom_group.newObject("Assembly::BomObject", "Bill of Materials") + else: + self.bomObj = App.activeDocument().addObject("Assembly::BomObject", "Bill of Materials") + + def export(self): + self.bomObj.recompute() + self.bomObj.ViewObject.exportAsFile() + + def eventFilter(self, watched, event): + if self.form is not None and watched == self.form.columnList: + if event.type() == QtCore.QEvent.ShortcutOverride: + if event.key() == QtCore.Qt.Key_Delete: + event.accept() # Accept the event only if the key is Delete + return True # Indicate that the event has been handled + return False + + elif event.type() == QtCore.QEvent.KeyPress: + if event.key() == QtCore.Qt.Key_Delete: + selected_indexes = self.form.columnList.selectedIndexes() + items_to_remove = [] + + for index in selected_indexes: + self.form.columnList.takeItem(index.row()) + + self.updateColumnList() + return True # Consume the event + + return super().eventFilter(watched, event) + + +if App.GuiUp: + Gui.addCommand("Assembly_CreateBom", CommandCreateBom()) diff --git a/src/Mod/Assembly/Gui/AppAssemblyGui.cpp b/src/Mod/Assembly/Gui/AppAssemblyGui.cpp index ad402d4c28..c76bfd1a8a 100644 --- a/src/Mod/Assembly/Gui/AppAssemblyGui.cpp +++ b/src/Mod/Assembly/Gui/AppAssemblyGui.cpp @@ -24,9 +24,12 @@ #include "PreCompiled.h" #include +#include #include #include "ViewProviderAssembly.h" +#include "ViewProviderBom.h" +#include "ViewProviderBomGroup.h" #include "ViewProviderJointGroup.h" #include "ViewProviderViewGroup.h" @@ -39,6 +42,15 @@ extern PyObject* initModule(); /* Python entry */ PyMOD_INIT_FUNC(AssemblyGui) { + // load dependent module + try { + Base::Interpreter().runString("import SpreadsheetGui"); + } + catch (const Base::Exception& e) { + PyErr_SetString(PyExc_ImportError, e.what()); + PyMOD_Return(nullptr); + } + PyObject* mod = AssemblyGui::initModule(); Base::Console().Log("Loading AssemblyGui module... done\n"); @@ -47,7 +59,9 @@ PyMOD_INIT_FUNC(AssemblyGui) // call PyType_Ready, otherwise we run into a segmentation fault, later on. // This function is responsible for adding inherited slots from a type's base class. - AssemblyGui::ViewProviderAssembly ::init(); + AssemblyGui::ViewProviderAssembly::init(); + AssemblyGui::ViewProviderBom::init(); + AssemblyGui::ViewProviderBomGroup::init(); AssemblyGui::ViewProviderJointGroup::init(); AssemblyGui::ViewProviderViewGroup::init(); diff --git a/src/Mod/Assembly/Gui/CMakeLists.txt b/src/Mod/Assembly/Gui/CMakeLists.txt index 3fb8d15623..80553d4197 100644 --- a/src/Mod/Assembly/Gui/CMakeLists.txt +++ b/src/Mod/Assembly/Gui/CMakeLists.txt @@ -10,6 +10,8 @@ set(AssemblyGui_LIBS Assembly PartDesign PartGui + Spreadsheet + SpreadsheetGui FreeCADGui ) @@ -38,6 +40,10 @@ SET(AssemblyGui_SRCS_Module PreCompiled.h ViewProviderAssembly.cpp ViewProviderAssembly.h + ViewProviderBom.cpp + ViewProviderBom.h + ViewProviderBomGroup.cpp + ViewProviderBomGroup.h ViewProviderJointGroup.cpp ViewProviderJointGroup.h ViewProviderViewGroup.cpp diff --git a/src/Mod/Assembly/Gui/Resources/Assembly.qrc b/src/Mod/Assembly/Gui/Resources/Assembly.qrc index b394ccd04d..c16225e37d 100644 --- a/src/Mod/Assembly/Gui/Resources/Assembly.qrc +++ b/src/Mod/Assembly/Gui/Resources/Assembly.qrc @@ -3,6 +3,8 @@ icons/Assembly_InsertLink.svg icons/preferences-assembly.svg icons/Assembly_ToggleGrounded.svg + icons/Assembly_BillOfMaterials.svg + icons/Assembly_BillOfMaterialsGroup.svg icons/Assembly_CreateJointAngle.svg icons/Assembly_CreateJointBall.svg icons/Assembly_CreateJointCylindrical.svg @@ -22,6 +24,7 @@ icons/Assembly_JointGroup.svg icons/Assembly_ExplodedView.svg icons/Assembly_ExplodedViewGroup.svg + panels/TaskAssemblyCreateBom.ui panels/TaskAssemblyCreateJoint.ui panels/TaskAssemblyInsertLink.ui panels/TaskAssemblyCreateView.ui diff --git a/src/Mod/Assembly/Gui/Resources/icons/Assembly_BillOfMaterials.svg b/src/Mod/Assembly/Gui/Resources/icons/Assembly_BillOfMaterials.svg new file mode 100644 index 0000000000..6d15e66f8b --- /dev/null +++ b/src/Mod/Assembly/Gui/Resources/icons/Assembly_BillOfMaterials.svg @@ -0,0 +1,600 @@ + + diff --git a/src/Mod/Assembly/Gui/Resources/icons/Assembly_BillOfMaterialsGroup.svg b/src/Mod/Assembly/Gui/Resources/icons/Assembly_BillOfMaterialsGroup.svg new file mode 100644 index 0000000000..4631c398e0 --- /dev/null +++ b/src/Mod/Assembly/Gui/Resources/icons/Assembly_BillOfMaterialsGroup.svg @@ -0,0 +1,298 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + 2015-07-04 + https://www.freecad.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Path/Gui/Resources/icons/Path-Stock.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Assembly/Gui/Resources/panels/TaskAssemblyCreateBom.ui b/src/Mod/Assembly/Gui/Resources/panels/TaskAssemblyCreateBom.ui new file mode 100644 index 0000000000..8726839896 --- /dev/null +++ b/src/Mod/Assembly/Gui/Resources/panels/TaskAssemblyCreateBom.ui @@ -0,0 +1,114 @@ + + + TaskAssemblyCreateView + + + + 0 + 0 + 376 + 387 + + + + Create Bill Of Materials + + + + + + If checked, Sub assemblies sub-components will be added to the bill of materials. + + + Detail sub-assemblies + + + true + + + BOMDetailSubAssemblies + + + Mod/Assembly + + + + + + + If checked, Parts sub-components will be added to the bill of materials. + + + Detail parts + + + true + + + BOMDetailParts + + + Mod/Assembly + + + + + + + If checked, only Part containers will be added to the bill of materials. Solids like PartDesign Bodies will be ignored. + + + Only parts + + + true + + + BOMOnlyParts + + + Mod/Assembly + + + + + + + Columns of the bill of materials + + + Columns + + + + + + + + + Add column + + + + + + + + + + Export + + + + + + + + Gui::PrefCheckBox + QCheckBox +
Gui/PrefWidgets.h
+
+
+ + +
diff --git a/src/Mod/Assembly/Gui/ViewProviderBom.cpp b/src/Mod/Assembly/Gui/ViewProviderBom.cpp new file mode 100644 index 0000000000..1ce7cbd18e --- /dev/null +++ b/src/Mod/Assembly/Gui/ViewProviderBom.cpp @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2023 Ondsel * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#include "PreCompiled.h" + +#ifndef _PreComp_ +#include +#endif + +#include +#include + +#include +#include + +#include + +#include "ViewProviderBom.h" + +using namespace AssemblyGui; + +PROPERTY_SOURCE(AssemblyGui::ViewProviderBom, SpreadsheetGui::ViewProviderSheet) + +ViewProviderBom::ViewProviderBom() +{} + +ViewProviderBom::~ViewProviderBom() = default; + +QIcon ViewProviderBom::getIcon() const +{ + return Gui::BitmapFactory().pixmap("Assembly_BillOfMaterials.svg"); +} + +bool ViewProviderBom::doubleClicked() +{ + try { + // Ensure the Python interpreter is initialized + if (!Py_IsInitialized()) { + Py_Initialize(); + } + + // Acquire the GIL (Global Interpreter Lock) + PyGILState_STATE gstate; + gstate = PyGILState_Ensure(); + std::string obj_name = getObject()->getNameInDocument(); + std::string doc_name = getObject()->getDocument()->getName(); + + // Call the Python function + std::string pythonCommand = "import CommandCreateBom\n" + "obj = App.getDocument('" + + doc_name + "').getObject('" + obj_name + + "')\n" + "Gui.Control.showDialog(CommandCreateBom.TaskAssemblyCreateBom(obj))"; + + PyRun_SimpleString(pythonCommand.c_str()); + + // Release the GIL + PyGILState_Release(gstate); + } + catch (...) { + PyErr_Print(); + } + + return true; +} diff --git a/src/Mod/Assembly/Gui/ViewProviderBom.h b/src/Mod/Assembly/Gui/ViewProviderBom.h new file mode 100644 index 0000000000..eb5cc8db8f --- /dev/null +++ b/src/Mod/Assembly/Gui/ViewProviderBom.h @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2023 Ondsel * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#ifndef ASSEMBLYGUI_VIEWPROVIDER_ViewProviderBom_H +#define ASSEMBLYGUI_VIEWPROVIDER_ViewProviderBom_H + +#include + +#include + + +namespace AssemblyGui +{ + +class AssemblyGuiExport ViewProviderBom: public SpreadsheetGui::ViewProviderSheet +{ + PROPERTY_HEADER_WITH_OVERRIDE(AssemblyGui::ViewProviderBom); + + +public: + ViewProviderBom(); + ~ViewProviderBom() override; + + QIcon getIcon() const override; + + bool doubleClicked() override; +}; + +} // namespace AssemblyGui + +#endif // ASSEMBLYGUI_VIEWPROVIDER_ViewProviderBom_H diff --git a/src/Mod/Assembly/Gui/ViewProviderBomGroup.cpp b/src/Mod/Assembly/Gui/ViewProviderBomGroup.cpp new file mode 100644 index 0000000000..980426cc11 --- /dev/null +++ b/src/Mod/Assembly/Gui/ViewProviderBomGroup.cpp @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2023 Ondsel * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#include "PreCompiled.h" + +#ifndef _PreComp_ +#endif + +#include +#include +#include +#include + +#include "ViewProviderBomGroup.h" + + +using namespace AssemblyGui; + +PROPERTY_SOURCE(AssemblyGui::ViewProviderBomGroup, Gui::ViewProviderDocumentObjectGroup) + +ViewProviderBomGroup::ViewProviderBomGroup() +{} + +ViewProviderBomGroup::~ViewProviderBomGroup() = default; + +QIcon ViewProviderBomGroup::getIcon() const +{ + return Gui::BitmapFactory().pixmap("Assembly_BillOfMaterialsGroup.svg"); +} diff --git a/src/Mod/Assembly/Gui/ViewProviderBomGroup.h b/src/Mod/Assembly/Gui/ViewProviderBomGroup.h new file mode 100644 index 0000000000..74448bda33 --- /dev/null +++ b/src/Mod/Assembly/Gui/ViewProviderBomGroup.h @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2023 Ondsel * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#ifndef ASSEMBLYGUI_VIEWPROVIDER_ViewProviderBomGroup_H +#define ASSEMBLYGUI_VIEWPROVIDER_ViewProviderBomGroup_H + +#include + +#include + + +namespace AssemblyGui +{ + +class AssemblyGuiExport ViewProviderBomGroup: public Gui::ViewProviderDocumentObjectGroup +{ + PROPERTY_HEADER_WITH_OVERRIDE(AssemblyGui::ViewProviderBomGroup); + +public: + ViewProviderBomGroup(); + ~ViewProviderBomGroup() override; + + /// deliver the icon shown in the tree view. Override from ViewProvider.h + QIcon getIcon() const override; + + // Prevent dragging of the joints and dropping things inside the joint group. + bool canDragObjects() const override + { + return false; + }; + bool canDropObjects() const override + { + return false; + }; + bool canDragAndDropObject(App::DocumentObject*) const override + { + return false; + }; + + // protected: + /// get called by the container whenever a property has been changed + // void onChanged(const App::Property* prop) override; +}; + +} // namespace AssemblyGui + +#endif // ASSEMBLYGUI_VIEWPROVIDER_ViewProviderBomGroup_H diff --git a/src/Mod/Assembly/InitGui.py b/src/Mod/Assembly/InitGui.py index f36a773745..369d777d84 100644 --- a/src/Mod/Assembly/InitGui.py +++ b/src/Mod/Assembly/InitGui.py @@ -63,7 +63,7 @@ class AssemblyWorkbench(Workbench): # load the builtin modules from PySide import QtCore, QtGui from PySide.QtCore import QT_TRANSLATE_NOOP - import CommandCreateAssembly, CommandInsertLink, CommandCreateJoint, CommandSolveAssembly, CommandExportASMT, CommandCreateView + import CommandCreateAssembly, CommandInsertLink, CommandCreateJoint, CommandSolveAssembly, CommandExportASMT, CommandCreateView, CommandCreateBom import Preferences FreeCADGui.addLanguagePath(":/translations") @@ -79,6 +79,7 @@ class AssemblyWorkbench(Workbench): "Assembly_InsertLink", "Assembly_SolveAssembly", "Assembly_CreateView", + "Assembly_CreateBom", ] cmdListMenuOnly = [ diff --git a/src/Mod/Assembly/UtilsAssembly.py b/src/Mod/Assembly/UtilsAssembly.py index 927c683864..431aa384ea 100644 --- a/src/Mod/Assembly/UtilsAssembly.py +++ b/src/Mod/Assembly/UtilsAssembly.py @@ -607,6 +607,20 @@ def color_from_unsigned(c): ] +def getBomGroup(assembly): + bom_group = None + + for obj in assembly.OutList: + if obj.TypeId == "Assembly::BomGroup": + bom_group = obj + break + + if not bom_group: + bom_group = assembly.newObject("Assembly::BomGroup", "Bills of Materials") + + return bom_group + + def getJointGroup(assembly): joint_group = None diff --git a/src/Mod/Spreadsheet/App/Sheet.cpp b/src/Mod/Spreadsheet/App/Sheet.cpp index dc7adef493..e730ca29f0 100644 --- a/src/Mod/Spreadsheet/App/Sheet.cpp +++ b/src/Mod/Spreadsheet/App/Sheet.cpp @@ -516,6 +516,11 @@ App::Range Sheet::getRange(const char* name, bool silent) const return cells.getRange(name, silent); } +std::tuple Sheet::getUsedRange() const +{ + return cells.getUsedRange(); +} + /** * @brief Get a map with column indices and widths. * @return Map with results. diff --git a/src/Mod/Spreadsheet/App/Sheet.h b/src/Mod/Spreadsheet/App/Sheet.h index 29ebe3528a..9b6ae0f6dc 100644 --- a/src/Mod/Spreadsheet/App/Sheet.h +++ b/src/Mod/Spreadsheet/App/Sheet.h @@ -199,6 +199,8 @@ public: App::Range getRange(const char* name, bool silent = false) const; + std::tuple getUsedRange() const; + std::map getColumnWidths() const; std::map getRowHeights() const; diff --git a/src/Mod/Spreadsheet/Gui/Command.cpp b/src/Mod/Spreadsheet/Gui/Command.cpp index 144f72c519..e644b0ffc6 100644 --- a/src/Mod/Spreadsheet/Gui/Command.cpp +++ b/src/Mod/Spreadsheet/Gui/Command.cpp @@ -39,6 +39,7 @@ #include "PropertiesDialog.h" #include "SpreadsheetView.h" +#include "ViewProviderSpreadsheet.h" //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -250,27 +251,10 @@ void CmdSpreadsheetExport::activated(int iMsg) if (sheetView) { Sheet* sheet = sheetView->getSheet(); - QString selectedFilter; - QString formatList = QObject::tr("CSV (*.csv *.CSV);;All (*)"); - QString fileName = Gui::FileDialog::getSaveFileName(Gui::getMainWindow(), - QObject::tr("Export file"), - QString(), - formatList, - &selectedFilter); - if (!fileName.isEmpty()) { - if (sheet) { - char delim, quote, escape; - std::string errMsg = "Export"; - bool isValid = sheet->getCharsFromPrefs(delim, quote, escape, errMsg); - - if (isValid) { - sheet->exportToFile(fileName.toStdString(), delim, quote, escape); - } - else { - Base::Console().Error(errMsg.c_str()); - return; - } - } + Gui::ViewProvider* vp = Gui::Application::Instance->getViewProvider(sheet); + auto* vps = dynamic_cast(vp); + if (vps) { + vps->exportAsFile(); } } } diff --git a/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheet.cpp b/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheet.cpp index 4bd72d45fc..ff91acff48 100644 --- a/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheet.cpp +++ b/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheet.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -102,11 +103,7 @@ QIcon ViewProviderSheet::getIcon() const bool ViewProviderSheet::setEdit(int ModNum) { if (ModNum == ViewProvider::Default) { - if (!this->view) { - showSpreadsheetView(); - view->viewAll(); - } - Gui::getMainWindow()->setActiveWindow(this->view); + showSheetMdi(); } return false; } @@ -123,12 +120,44 @@ bool ViewProviderSheet::doubleClicked() Gui::Command::assureWorkbench("SpreadsheetWorkbench"); } + showSheetMdi(); + return true; +} + +void ViewProviderSheet::showSheetMdi() +{ if (!this->view) { showSpreadsheetView(); view->viewAll(); } Gui::getMainWindow()->setActiveWindow(this->view); - return true; +} + +void ViewProviderSheet::exportAsFile() +{ + auto* sheet = static_cast(getObject()); + QString selectedFilter; + QString formatList = QObject::tr("CSV (*.csv *.CSV);;All (*)"); + QString fileName = Gui::FileDialog::getSaveFileName(Gui::getMainWindow(), + QObject::tr("Export file"), + QString(), + formatList, + &selectedFilter); + if (!fileName.isEmpty()) { + if (sheet) { + char delim, quote, escape; + std::string errMsg = "Export"; + bool isValid = sheet->getCharsFromPrefs(delim, quote, escape, errMsg); + + if (isValid) { + sheet->exportToFile(fileName.toStdString(), delim, quote, escape); + } + else { + Base::Console().Error(errMsg.c_str()); + return; + } + } + } } void ViewProviderSheet::setupContextMenu(QMenu* menu, QObject* receiver, const char* member) diff --git a/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheet.h b/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheet.h index 98b61f127c..eed33c61ee 100644 --- a/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheet.h +++ b/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheet.h @@ -78,6 +78,10 @@ public: PyObject* getPyObject() override; + void showSheetMdi(); + + void exportAsFile(); + protected: SheetView* showSpreadsheetView(); void updateData(const App::Property* prop) override; diff --git a/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheetPy.xml b/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheetPy.xml index 2434e12d36..1dc830cbe9 100644 --- a/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheetPy.xml +++ b/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheetPy.xml @@ -20,5 +20,27 @@ Get access to the sheet view + + + + Create (if necessary) and switch to the Spreadsheet MDI. + + showSheetMdi() + + Returns: None + + + + + + + Export the sheet as a file. + + exportAsFile() + + Returns: None + + + diff --git a/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheetPyImp.cpp b/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheetPyImp.cpp index 6b765a81d8..a52ef87c86 100644 --- a/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheetPyImp.cpp +++ b/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheetPyImp.cpp @@ -59,3 +59,21 @@ int ViewProviderSpreadsheetPy::setCustomAttributes(const char* /*attr*/, PyObjec { return 0; } + +PyObject* ViewProviderSpreadsheetPy::showSheetMdi(PyObject* args) +{ + if (!PyArg_ParseTuple(args, "")) { + return nullptr; + } + this->getViewProviderSheetPtr()->showSheetMdi(); + Py_Return; +} + +PyObject* ViewProviderSpreadsheetPy::exportAsFile(PyObject* args) +{ + if (!PyArg_ParseTuple(args, "")) { + return nullptr; + } + this->getViewProviderSheetPtr()->exportAsFile(); + Py_Return; +}