| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | #include <cmath> |
| | #include <vector> |
| |
|
| | #include <App/Application.h> |
| | #include <App/Document.h> |
| | #include <App/DocumentObjectGroup.h> |
| | #include <App/FeaturePythonPyImp.h> |
| | #include <App/Link.h> |
| | #include <App/PropertyPythonObject.h> |
| | #include <App/Range.h> |
| | #include <Base/Console.h> |
| | #include <Base/Placement.h> |
| | #include <Base/Rotation.h> |
| | #include <Base/Tools.h> |
| | #include <Base/Interpreter.h> |
| | #include <QObject> |
| |
|
| | #include <Mod/Part/App/PartFeature.h> |
| | #include <Mod/PartDesign/App/Body.h> |
| | #include <Mod/Spreadsheet/App/Cell.h> |
| |
|
| | #include "AssemblyObject.h" |
| | #include "AssemblyLink.h" |
| | #include "BomObject.h" |
| | #include "BomObjectPy.h" |
| |
|
| |
|
| | using namespace Assembly; |
| |
|
| | |
| |
|
| | 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())) { |
| | |
| | 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() |
| | { |
| | |
| | dataElements.clear(); |
| | std::tuple<App::CellAddress, App::CellAddress> res = getUsedRange(); |
| | int maxRow = std::get<1>(res).row(); |
| | int nameColIndex = getColumnIndex("Name"); |
| |
|
| | for (int row = 1; row <= maxRow; ++row) { |
| |
|
| | size_t col = 0; |
| | |
| | |
| | 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") { |
| | |
| | |
| | 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(); |
| | obj_list.clear(); |
| | size_t row = 0; |
| | size_t col = 0; |
| |
|
| | |
| | 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<App::DocumentObject*> objs, |
| | size_t& row, |
| | std::string index |
| | ) |
| | { |
| | int quantityColIndex = getColumnIndex("Quantity"); |
| | bool hasQuantityCol = hasQuantityColumn(); |
| |
|
| | size_t siblingsInitialRow = row; |
| |
|
| | if (index != "") { |
| | index = index + "."; |
| | } |
| |
|
| | size_t sub_i = 1; |
| |
|
| | for (auto* child : objs) { |
| | if (!child) { |
| | continue; |
| | } |
| | if (auto* asmLink = freecad_cast<AssemblyLink*>(child)) { |
| | child = asmLink->getLinkedAssembly(); |
| | if (!child) { |
| | continue; |
| | } |
| | } |
| | else if (child->isDerivedFrom<App::Link>()) { |
| | child = static_cast<App::Link*>(child)->getLinkedObject(); |
| | if (!child) { |
| | continue; |
| | } |
| | } |
| |
|
| | if (!child->isDerivedFrom<AssemblyObject>() && !child->isDerivedFrom<App::Part>() |
| | && !(child->isDerivedFrom<Part::Feature>() && !onlyParts.getValue())) { |
| | continue; |
| | } |
| |
|
| | if (hasQuantityCol && row != siblingsInitialRow) { |
| | |
| | |
| | |
| | bool found = false; |
| | for (size_t i = siblingsInitialRow; i <= row; ++i) { |
| | size_t idInList = i - 1; |
| | if (idInList < obj_list.size() && child == obj_list[idInList]) { |
| |
|
| | 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; |
| | } |
| | } |
| |
|
| | std::string sub_index = index + std::to_string(sub_i); |
| | ++sub_i; |
| |
|
| | addObjectToBom(child, row, sub_index); |
| | ++row; |
| |
|
| | if ((child->isDerivedFrom<AssemblyObject>() && detailSubAssemblies.getValue()) |
| | || (!child->isDerivedFrom<AssemblyObject>() && child->isDerivedFrom<App::Part>() |
| | && detailParts.getValue())) { |
| | addObjectChildrenToBom(child->getOutList(), row, sub_index); |
| | } |
| | } |
| | } |
| |
|
| | void BomObject::addObjectToBom(App::DocumentObject* obj, size_t row, std::string index) |
| | { |
| | obj_list.push_back(obj); |
| | size_t col = 0; |
| | for (auto& columnName : columnsNames.getValues()) { |
| | if (columnName == "Index") { |
| | setCell(App::CellAddress(row, col), (std::string("'") + 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 if (columnName.starts_with(".")) { |
| | |
| | |
| | std::string baseName = columnName.substr(1); |
| |
|
| | auto propertyValue = getBomPropertyValue(obj, baseName); |
| | if (!propertyValue.empty()) { |
| | setCell(App::CellAddress(row, col), propertyValue.c_str()); |
| | } |
| | } |
| | else { |
| | |
| | for (auto& el : dataElements) { |
| | if (el.objName == obj->Label.getValue() && el.columnName == columnName) { |
| | setCell(App::CellAddress(row, col), el.value.c_str()); |
| | break; |
| | } |
| | } |
| | } |
| | ++col; |
| | } |
| | } |
| |
|
| | std::string BomObject::getBomPropertyValue(App::DocumentObject* obj, const std::string& baseName) |
| | { |
| | App::Property* prop = obj->getPropertyByName(baseName.c_str()); |
| |
|
| | if (!prop) { |
| | Base::Console().warning("Property not found: %s\n", baseName.c_str()); |
| | return QObject::tr("N/A").toStdString(); |
| | } |
| |
|
| | |
| | if (auto propStr = freecad_cast<App::PropertyString*>(prop)) { |
| | return propStr->getValue(); |
| | } |
| | if (auto propQuantity = freecad_cast<App::PropertyQuantity*>(prop)) { |
| | return propQuantity->getQuantityValue().getUserString(); |
| | } |
| | if (auto propEnum = freecad_cast<App::PropertyEnumeration*>(prop)) { |
| | return propEnum->getValueAsString(); |
| | } |
| | if (auto propFloat = freecad_cast<App::PropertyFloat*>(prop)) { |
| | return std::to_string(propFloat->getValue()); |
| | } |
| | if (auto propInt = freecad_cast<App::PropertyInteger*>(prop)) { |
| | return std::to_string(propInt->getValue()); |
| | } |
| | if (auto propBool = freecad_cast<App::PropertyBool*>(prop)) { |
| | return propBool->getValue() ? "True" : "False"; |
| | } |
| |
|
| | Base::Console().warning("Property type not supported for: %s\n", prop->getName()); |
| | return QObject::tr("Not supported").toStdString(); |
| | } |
| |
|
| | AssemblyObject* BomObject::getAssembly() const |
| | { |
| | for (auto& obj : getInList()) { |
| | if (obj->isDerivedFrom<AssemblyObject>()) { |
| | return static_cast<AssemblyObject*>(obj); |
| | } |
| | } |
| | return nullptr; |
| | } |
| |
|
| | bool BomObject::hasQuantityColumn() const |
| | { |
| | for (auto& columnName : columnsNames.getValues()) { |
| | if (columnName == "Quantity") { |
| | return true; |
| | } |
| | } |
| | return false; |
| | } |
| |
|
| | std::string Assembly::BomObject::getText(size_t row, size_t col) const |
| | { |
| | const Spreadsheet::Cell* cell = getCell(App::CellAddress(row, col)); |
| | std::string cellName; |
| | if (cell) { |
| | cell->getStringContent(cellName); |
| |
|
| | |
| | if (!cellName.empty() && cellName.front() == '\'') { |
| | cellName.erase(0, 1); |
| | } |
| | } |
| |
|
| | return cellName; |
| | } |
| |
|
| | int BomObject::getColumnIndex(std::string name) const |
| | { |
| | int col = 0; |
| | for (auto& columnName : columnsNames.getValues()) { |
| | if (columnName == name) { |
| | return col; |
| | } |
| | ++col; |
| | } |
| | return -1; |
| | } |
| |
|