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.svgicons/preferences-assembly.svgicons/Assembly_ToggleGrounded.svg
+ icons/Assembly_BillOfMaterials.svg
+ icons/Assembly_BillOfMaterialsGroup.svgicons/Assembly_CreateJointAngle.svgicons/Assembly_CreateJointBall.svgicons/Assembly_CreateJointCylindrical.svg
@@ -22,6 +24,7 @@
icons/Assembly_JointGroup.svgicons/Assembly_ExplodedView.svgicons/Assembly_ExplodedViewGroup.svg
+ panels/TaskAssemblyCreateBom.uipanels/TaskAssemblyCreateJoint.uipanels/TaskAssemblyInsertLink.uipanels/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 @@
+
+
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;
+}