From 3d0df45cd46f54bec8ec8fba64314838e5c955a8 Mon Sep 17 00:00:00 2001 From: PaddleStroke Date: Tue, 10 Feb 2026 16:10:03 +0100 Subject: [PATCH] Assembly: BOM: take mirrored state of links in account. (#26113) --- src/Mod/Assembly/App/BomObject.cpp | 51 +++++++++++++++++++++++++++--- src/Mod/Assembly/App/BomObject.h | 6 +++- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/Mod/Assembly/App/BomObject.cpp b/src/Mod/Assembly/App/BomObject.cpp index 54fcb4b14f..b92eaedbc7 100644 --- a/src/Mod/Assembly/App/BomObject.cpp +++ b/src/Mod/Assembly/App/BomObject.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -144,9 +145,15 @@ void BomObject::generateBOM() saveCustomColumnData(); clearAll(); obj_list.clear(); + obj_mirrored_list.clear(); size_t row = 0; size_t col = 0; + auto hGrp = App::GetApplication().GetParameterGroupByPath( + "User parameter:BaseApp/Preferences/Mod/Assembly" + ); + mirroredSuffix = hGrp->GetASCII("BomMirroredSuffix", " (mirrored)"); + // Populate headers for (auto& columnName : columnsNames.getValues()) { setCell(App::CellAddress(row, col), columnName.c_str()); @@ -184,6 +191,9 @@ void BomObject::addObjectChildrenToBom( if (!child) { continue; } + + bool isMirrored = isObjMirrored(child); + if (auto* asmLink = freecad_cast(child)) { child = asmLink->getLinkedAssembly(); if (!child) { @@ -206,10 +216,14 @@ void BomObject::addObjectChildrenToBom( // 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. + // We also check if the Mirror state matches. Mirrored parts should not group with + // non-mirrored parts. bool found = false; for (size_t i = siblingsInitialRow; i <= row; ++i) { size_t idInList = i - 1; // -1 for the header - if (idInList < obj_list.size() && child == obj_list[idInList]) { + if (idInList < obj_list.size() && child == obj_list[idInList] + && idInList < obj_mirrored_list.size() + && isMirrored == obj_mirrored_list[idInList]) { int qty = std::stoi(getText(i, quantityColIndex)) + 1; setCell(App::CellAddress(i, quantityColIndex), std::to_string(qty).c_str()); @@ -225,7 +239,7 @@ void BomObject::addObjectChildrenToBom( std::string sub_index = index + std::to_string(sub_i); ++sub_i; - addObjectToBom(child, row, sub_index); + addObjectToBom(child, row, sub_index, isMirrored); ++row; if ((child->isDerivedFrom() && detailSubAssemblies.getValue()) @@ -236,7 +250,26 @@ void BomObject::addObjectChildrenToBom( } } -void BomObject::addObjectToBom(App::DocumentObject* obj, size_t row, std::string index) +bool BomObject::isObjMirrored(App::DocumentObject* obj) +{ + // Determine mirror state based on Scale properties. + // We multiply scales to handle nested mirroring (e.g., Mirrored LinkElement inside Mirrored + // LinkGroup = Normal). + double accumulatedScale = 1.0; + if (auto element = static_cast(obj)) { + accumulatedScale *= element->Scale.getValue(); + + if (auto group = element->getLinkGroup()) { + accumulatedScale *= group->Scale.getValue(); + } + } + else if (auto link = static_cast(obj)) { + accumulatedScale *= link->Scale.getValue(); + } + return accumulatedScale < 0.0; +} + +void BomObject::addObjectToBom(App::DocumentObject* obj, size_t row, std::string index, bool isMirrored) { obj_list.push_back(obj); size_t col = 0; @@ -245,7 +278,17 @@ void BomObject::addObjectToBom(App::DocumentObject* obj, size_t row, std::string setCell(App::CellAddress(row, col), (std::string("'") + index).c_str()); } else if (columnName == "Name") { - setCell(App::CellAddress(row, col), obj->Label.getValue()); + std::string name = obj->Label.getValue(); + // Distinctly label mirrored parts so they are identifiable in the BOM + if (isMirrored) { + if (auto* linkedObj = obj->getLinkedObject()) { + // We add a suffix only if the label is the same. + if (name == linkedObj->Label.getValue()) { + name += mirroredSuffix; + } + } + } + setCell(App::CellAddress(row, col), name.c_str()); } else if (columnName == "File Name") { setCell(App::CellAddress(row, col), obj->getDocument()->getFileName()); diff --git a/src/Mod/Assembly/App/BomObject.h b/src/Mod/Assembly/App/BomObject.h index 2ea18d81b7..3d18954898 100644 --- a/src/Mod/Assembly/App/BomObject.h +++ b/src/Mod/Assembly/App/BomObject.h @@ -76,9 +76,10 @@ public: App::DocumentObjectExecReturn* execute() override; void generateBOM(); - void addObjectToBom(App::DocumentObject* obj, size_t row, std::string index); + void addObjectToBom(App::DocumentObject* obj, size_t row, std::string index, bool isMirrored = false); void addObjectChildrenToBom(std::vector objs, size_t& row, std::string index); void saveCustomColumnData(); + bool isObjMirrored(App::DocumentObject* obj); AssemblyObject* getAssembly() const; @@ -93,9 +94,12 @@ public: std::vector dataElements; std::vector obj_list; + std::vector obj_mirrored_list; private: std::string getBomPropertyValue(App::DocumentObject* obj, const std::string& baseName); + + std::string mirroredSuffix; };