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 ());
0 commit comments