Skip to content

Commit 3d0df45

Browse files
authored
Assembly: BOM: take mirrored state of links in account. (FreeCAD#26113)
1 parent bf51a00 commit 3d0df45

File tree

2 files changed

+52
-5
lines changed

2 files changed

+52
-5
lines changed

src/Mod/Assembly/App/BomObject.cpp

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include <App/PropertyPythonObject.h>
3333
#include <App/Range.h>
3434
#include <Base/Console.h>
35+
#include <Base/Parameter.h>
3536
#include <Base/Placement.h>
3637
#include <Base/Rotation.h>
3738
#include <Base/Tools.h>
@@ -144,9 +145,15 @@ void BomObject::generateBOM()
144145
saveCustomColumnData();
145146
clearAll();
146147
obj_list.clear();
148+
obj_mirrored_list.clear();
147149
size_t row = 0;
148150
size_t col = 0;
149151

152+
auto hGrp = App::GetApplication().GetParameterGroupByPath(
153+
"User parameter:BaseApp/Preferences/Mod/Assembly"
154+
);
155+
mirroredSuffix = hGrp->GetASCII("BomMirroredSuffix", " (mirrored)");
156+
150157
// Populate headers
151158
for (auto& columnName : columnsNames.getValues()) {
152159
setCell(App::CellAddress(row, col), columnName.c_str());
@@ -184,6 +191,9 @@ void BomObject::addObjectChildrenToBom(
184191
if (!child) {
185192
continue;
186193
}
194+
195+
bool isMirrored = isObjMirrored(child);
196+
187197
if (auto* asmLink = freecad_cast<AssemblyLink*>(child)) {
188198
child = asmLink->getLinkedAssembly();
189199
if (!child) {
@@ -206,10 +216,14 @@ void BomObject::addObjectChildrenToBom(
206216
// Check if the object is not already in (case of links). And if so just increment.
207217
// Note: an object can be used in several parts. In which case we do no want to blindly
208218
// increment.
219+
// We also check if the Mirror state matches. Mirrored parts should not group with
220+
// non-mirrored parts.
209221
bool found = false;
210222
for (size_t i = siblingsInitialRow; i <= row; ++i) {
211223
size_t idInList = i - 1; // -1 for the header
212-
if (idInList < obj_list.size() && child == obj_list[idInList]) {
224+
if (idInList < obj_list.size() && child == obj_list[idInList]
225+
&& idInList < obj_mirrored_list.size()
226+
&& isMirrored == obj_mirrored_list[idInList]) {
213227

214228
int qty = std::stoi(getText(i, quantityColIndex)) + 1;
215229
setCell(App::CellAddress(i, quantityColIndex), std::to_string(qty).c_str());
@@ -225,7 +239,7 @@ void BomObject::addObjectChildrenToBom(
225239
std::string sub_index = index + std::to_string(sub_i);
226240
++sub_i;
227241

228-
addObjectToBom(child, row, sub_index);
242+
addObjectToBom(child, row, sub_index, isMirrored);
229243
++row;
230244

231245
if ((child->isDerivedFrom<AssemblyObject>() && detailSubAssemblies.getValue())
@@ -236,7 +250,26 @@ void BomObject::addObjectChildrenToBom(
236250
}
237251
}
238252

239-
void BomObject::addObjectToBom(App::DocumentObject* obj, size_t row, std::string index)
253+
bool BomObject::isObjMirrored(App::DocumentObject* obj)
254+
{
255+
// Determine mirror state based on Scale properties.
256+
// We multiply scales to handle nested mirroring (e.g., Mirrored LinkElement inside Mirrored
257+
// LinkGroup = Normal).
258+
double accumulatedScale = 1.0;
259+
if (auto element = static_cast<App::LinkElement*>(obj)) {
260+
accumulatedScale *= element->Scale.getValue();
261+
262+
if (auto group = element->getLinkGroup()) {
263+
accumulatedScale *= group->Scale.getValue();
264+
}
265+
}
266+
else if (auto link = static_cast<App::Link*>(obj)) {
267+
accumulatedScale *= link->Scale.getValue();
268+
}
269+
return accumulatedScale < 0.0;
270+
}
271+
272+
void BomObject::addObjectToBom(App::DocumentObject* obj, size_t row, std::string index, bool isMirrored)
240273
{
241274
obj_list.push_back(obj);
242275
size_t col = 0;
@@ -245,7 +278,17 @@ void BomObject::addObjectToBom(App::DocumentObject* obj, size_t row, std::string
245278
setCell(App::CellAddress(row, col), (std::string("'") + index).c_str());
246279
}
247280
else if (columnName == "Name") {
248-
setCell(App::CellAddress(row, col), obj->Label.getValue());
281+
std::string name = obj->Label.getValue();
282+
// Distinctly label mirrored parts so they are identifiable in the BOM
283+
if (isMirrored) {
284+
if (auto* linkedObj = obj->getLinkedObject()) {
285+
// We add a suffix only if the label is the same.
286+
if (name == linkedObj->Label.getValue()) {
287+
name += mirroredSuffix;
288+
}
289+
}
290+
}
291+
setCell(App::CellAddress(row, col), name.c_str());
249292
}
250293
else if (columnName == "File Name") {
251294
setCell(App::CellAddress(row, col), obj->getDocument()->getFileName());

src/Mod/Assembly/App/BomObject.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,10 @@ class AssemblyExport BomObject: public Spreadsheet::Sheet
7676
App::DocumentObjectExecReturn* execute() override;
7777

7878
void generateBOM();
79-
void addObjectToBom(App::DocumentObject* obj, size_t row, std::string index);
79+
void addObjectToBom(App::DocumentObject* obj, size_t row, std::string index, bool isMirrored = false);
8080
void addObjectChildrenToBom(std::vector<App::DocumentObject*> objs, size_t& row, std::string index);
8181
void saveCustomColumnData();
82+
bool isObjMirrored(App::DocumentObject* obj);
8283

8384
AssemblyObject* getAssembly() const;
8485

@@ -93,9 +94,12 @@ class AssemblyExport BomObject: public Spreadsheet::Sheet
9394

9495
std::vector<BomDataElement> dataElements;
9596
std::vector<App::DocumentObject*> obj_list;
97+
std::vector<bool> obj_mirrored_list;
9698

9799
private:
98100
std::string getBomPropertyValue(App::DocumentObject* obj, const std::string& baseName);
101+
102+
std::string mirroredSuffix;
99103
};
100104

101105

0 commit comments

Comments
 (0)