From 5c29d5647d0056649308653a13feeb98a455739d Mon Sep 17 00:00:00 2001
From: Matthias Puchner <github@mpuchner.de>
Date: Wed, 8 Dec 2021 20:13:36 +0100
Subject: [PATCH 01/15] fix warning "unused param"

---
 GUI/Model/Sample/SampleValidator.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/GUI/Model/Sample/SampleValidator.cpp b/GUI/Model/Sample/SampleValidator.cpp
index 00060132a8d..f69311d2af6 100644
--- a/GUI/Model/Sample/SampleValidator.cpp
+++ b/GUI/Model/Sample/SampleValidator.cpp
@@ -42,8 +42,8 @@ void SampleValidator::validateItem(const SessionItem* item)
 
 void SampleValidator::validateInterferences(const LayerItem* layer)
 {
-#ifdef WIN32
     ASSERT(layer);
+#ifdef WIN32
 
     int iLayout = 0;
     for (const auto* layout : layer->layouts()) {
-- 
GitLab


From ca559fcd19fa1170302e2d965752a26ce21a0b48 Mon Sep 17 00:00:00 2001
From: Matthias Puchner <github@mpuchner.de>
Date: Thu, 9 Dec 2021 06:23:12 +0100
Subject: [PATCH 02/15] ensure correct layer-thickness when creating sample
 (GUI->domain)

---
 GUI/Model/To/DomainObjectBuilder.cpp | 8 +++++---
 GUI/Model/To/DomainObjectBuilder.h   | 2 +-
 GUI/Model/To/ToDomain.cpp            | 7 ++++---
 GUI/Model/To/ToDomain.h              | 2 +-
 4 files changed, 11 insertions(+), 8 deletions(-)

diff --git a/GUI/Model/To/DomainObjectBuilder.cpp b/GUI/Model/To/DomainObjectBuilder.cpp
index 13984572b0a..20a65f09846 100644
--- a/GUI/Model/To/DomainObjectBuilder.cpp
+++ b/GUI/Model/To/DomainObjectBuilder.cpp
@@ -26,7 +26,8 @@ GUI::Model::DomainObjectBuilder::buildMultiLayer(const MultiLayerItem& item)
 {
     auto P_multilayer = GUI::Transform::ToDomain::createMultiLayer(item);
     for (auto* layerItem : item.layers()) {
-        auto P_layer = buildLayer(*layerItem);
+        const bool isFirstOrLastLayer = layerItem == item.layers().first() || item.layers().last();
+        auto P_layer = buildLayer(*layerItem, isFirstOrLastLayer);
         const auto roughness = layerItem->roughness().currentItem();
         auto P_roughness = GUI::Transform::ToDomain::createLayerRoughness(roughness);
         if (P_layer) {
@@ -39,9 +40,10 @@ GUI::Model::DomainObjectBuilder::buildMultiLayer(const MultiLayerItem& item)
     return P_multilayer;
 }
 
-std::unique_ptr<Layer> GUI::Model::DomainObjectBuilder::buildLayer(const LayerItem& item)
+std::unique_ptr<Layer> GUI::Model::DomainObjectBuilder::buildLayer(const LayerItem& item,
+                                                                   bool isFirstOrLastLayer)
 {
-    auto P_layer = GUI::Transform::ToDomain::createLayer(item);
+    auto P_layer = GUI::Transform::ToDomain::createLayer(item, isFirstOrLastLayer);
     for (ParticleLayoutItem* layout : item.layouts()) {
         auto P_layout = buildParticleLayout(*layout);
         if (P_layout)
diff --git a/GUI/Model/To/DomainObjectBuilder.h b/GUI/Model/To/DomainObjectBuilder.h
index 6221e2b81c7..132010d55d5 100644
--- a/GUI/Model/To/DomainObjectBuilder.h
+++ b/GUI/Model/To/DomainObjectBuilder.h
@@ -32,7 +32,7 @@ class ICoordSystem;
 namespace GUI::Model::DomainObjectBuilder {
 
 std::unique_ptr<MultiLayer> buildMultiLayer(const MultiLayerItem& item);
-std::unique_ptr<Layer> buildLayer(const LayerItem& item);
+std::unique_ptr<Layer> buildLayer(const LayerItem& item, bool isFirstOrLastLayer);
 std::unique_ptr<ParticleLayout> buildParticleLayout(const ParticleLayoutItem& item);
 
 } // namespace GUI::Model::DomainObjectBuilder
diff --git a/GUI/Model/To/ToDomain.cpp b/GUI/Model/To/ToDomain.cpp
index b7184922a69..9cc724da0ee 100644
--- a/GUI/Model/To/ToDomain.cpp
+++ b/GUI/Model/To/ToDomain.cpp
@@ -39,10 +39,11 @@ std::unique_ptr<MultiLayer> GUI::Transform::ToDomain::createMultiLayer(const Mul
     return P_multilayer;
 }
 
-std::unique_ptr<Layer> GUI::Transform::ToDomain::createLayer(const LayerItem& item)
+std::unique_ptr<Layer> GUI::Transform::ToDomain::createLayer(const LayerItem& item,
+                                                             bool isFirstOrLastLayer)
 {
-    auto P_layer =
-        std::make_unique<Layer>(*item.materialItem()->createMaterial(), item.thickness());
+    auto P_layer = std::make_unique<Layer>(*item.materialItem()->createMaterial(),
+                                           isFirstOrLastLayer ? 0.0 : item.thickness());
     P_layer->setNumberOfSlices(item.numSlices());
     return P_layer;
 }
diff --git a/GUI/Model/To/ToDomain.h b/GUI/Model/To/ToDomain.h
index c2f7607f978..3094cc184c4 100644
--- a/GUI/Model/To/ToDomain.h
+++ b/GUI/Model/To/ToDomain.h
@@ -37,7 +37,7 @@ class LayerBasicRoughnessItem;
 namespace GUI::Transform::ToDomain {
 
 std::unique_ptr<IParticle> createIParticle(const SessionItem& item);
-std::unique_ptr<Layer> createLayer(const LayerItem& item);
+std::unique_ptr<Layer> createLayer(const LayerItem& item, bool isFirstOrLastLayer);
 std::unique_ptr<LayerRoughness> createLayerRoughness(
     const std::variant<LayerZeroRoughnessItem*, LayerBasicRoughnessItem*>& roughness);
 std::unique_ptr<MultiLayer> createMultiLayer(const MultiLayerItem& item);
-- 
GitLab


From e59c67d1445f11a2fccaf8c9d91ea70254239708 Mon Sep 17 00:00:00 2001
From: Matthias Puchner <github@mpuchner.de>
Date: Thu, 9 Dec 2021 06:27:11 +0100
Subject: [PATCH 03/15] ensure correct roughness when creating sample
 (GUI->domain)

---
 GUI/Model/To/DomainObjectBuilder.cpp | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/GUI/Model/To/DomainObjectBuilder.cpp b/GUI/Model/To/DomainObjectBuilder.cpp
index 20a65f09846..bc69ebd0f74 100644
--- a/GUI/Model/To/DomainObjectBuilder.cpp
+++ b/GUI/Model/To/DomainObjectBuilder.cpp
@@ -22,16 +22,17 @@
 #include "GUI/Util/Error.h"
 
 std::unique_ptr<MultiLayer>
-GUI::Model::DomainObjectBuilder::buildMultiLayer(const MultiLayerItem& item)
+GUI::Model::DomainObjectBuilder::buildMultiLayer(const MultiLayerItem& multiLayerItem)
 {
-    auto P_multilayer = GUI::Transform::ToDomain::createMultiLayer(item);
-    for (auto* layerItem : item.layers()) {
-        const bool isFirstOrLastLayer = layerItem == item.layers().first() || item.layers().last();
-        auto P_layer = buildLayer(*layerItem, isFirstOrLastLayer);
-        const auto roughness = layerItem->roughness().currentItem();
-        auto P_roughness = GUI::Transform::ToDomain::createLayerRoughness(roughness);
+    auto P_multilayer = GUI::Transform::ToDomain::createMultiLayer(multiLayerItem);
+    for (auto* layerItem : multiLayerItem.layers()) {
+        const bool isFirstLayer = layerItem == multiLayerItem.layers().first();
+        const bool isLastLayer = layerItem == multiLayerItem.layers().last();
+        auto P_layer = buildLayer(*layerItem, isFirstLayer || isLastLayer);
         if (P_layer) {
-            if (P_roughness)
+            const auto roughness = layerItem->roughness().currentItem();
+            auto P_roughness = GUI::Transform::ToDomain::createLayerRoughness(roughness);
+            if (P_roughness && !isLastLayer)
                 P_multilayer->addLayerWithTopRoughness(*P_layer, *P_roughness);
             else
                 P_multilayer->addLayer(*P_layer);
-- 
GitLab


From f4683b9f627785e904b03e417c660287a50dd90b Mon Sep 17 00:00:00 2001
From: Matthias Puchner <github@mpuchner.de>
Date: Thu, 9 Dec 2021 06:42:01 +0100
Subject: [PATCH 04/15] cleanup/simplify ToDomain (namespaces, files)

---
 GUI/Model/To/DomainObjectBuilder.cpp       |  78 ----------------
 GUI/Model/To/DomainObjectBuilder.h         |  40 --------
 GUI/Model/To/DomainSimulationBuilder.cpp   |   4 +-
 GUI/Model/To/ToDomain.cpp                  | 104 +++++++++++++++++----
 GUI/Model/To/ToDomain.h                    |  20 +---
 GUI/View/SampleDesigner/ScriptPanel.cpp    |   5 +-
 Tests/Unit/GUI/TestLayerRoughnessItems.cpp |   1 +
 7 files changed, 94 insertions(+), 158 deletions(-)
 delete mode 100644 GUI/Model/To/DomainObjectBuilder.cpp
 delete mode 100644 GUI/Model/To/DomainObjectBuilder.h

diff --git a/GUI/Model/To/DomainObjectBuilder.cpp b/GUI/Model/To/DomainObjectBuilder.cpp
deleted file mode 100644
index bc69ebd0f74..00000000000
--- a/GUI/Model/To/DomainObjectBuilder.cpp
+++ /dev/null
@@ -1,78 +0,0 @@
-//  ************************************************************************************************
-//
-//  BornAgain: simulate and fit reflection and scattering
-//
-//! @file      GUI/Model/To/DomainObjectBuilder.cpp
-//! @brief     Implements DomainObjectBuilder namespace
-//!
-//! @homepage  http://www.bornagainproject.org
-//! @license   GNU General Public License v3 or higher (see COPYING)
-//! @copyright Forschungszentrum Jülich GmbH 2018
-//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
-//
-//  ************************************************************************************************
-
-#include "GUI/Model/To/DomainObjectBuilder.h"
-#include "GUI/Model/Instrument/InstrumentItems.h"
-#include "GUI/Model/Sample/InterferenceItems.h"
-#include "GUI/Model/Sample/LayerItem.h"
-#include "GUI/Model/Sample/MultiLayerItem.h"
-#include "GUI/Model/Sample/ParticleLayoutItem.h"
-#include "GUI/Model/To/ToDomain.h"
-#include "GUI/Util/Error.h"
-
-std::unique_ptr<MultiLayer>
-GUI::Model::DomainObjectBuilder::buildMultiLayer(const MultiLayerItem& multiLayerItem)
-{
-    auto P_multilayer = GUI::Transform::ToDomain::createMultiLayer(multiLayerItem);
-    for (auto* layerItem : multiLayerItem.layers()) {
-        const bool isFirstLayer = layerItem == multiLayerItem.layers().first();
-        const bool isLastLayer = layerItem == multiLayerItem.layers().last();
-        auto P_layer = buildLayer(*layerItem, isFirstLayer || isLastLayer);
-        if (P_layer) {
-            const auto roughness = layerItem->roughness().currentItem();
-            auto P_roughness = GUI::Transform::ToDomain::createLayerRoughness(roughness);
-            if (P_roughness && !isLastLayer)
-                P_multilayer->addLayerWithTopRoughness(*P_layer, *P_roughness);
-            else
-                P_multilayer->addLayer(*P_layer);
-        }
-    }
-    return P_multilayer;
-}
-
-std::unique_ptr<Layer> GUI::Model::DomainObjectBuilder::buildLayer(const LayerItem& item,
-                                                                   bool isFirstOrLastLayer)
-{
-    auto P_layer = GUI::Transform::ToDomain::createLayer(item, isFirstOrLastLayer);
-    for (ParticleLayoutItem* layout : item.layouts()) {
-        auto P_layout = buildParticleLayout(*layout);
-        if (P_layout)
-            P_layer->addLayout(*P_layout);
-    }
-    return P_layer;
-}
-
-std::unique_ptr<ParticleLayout>
-GUI::Model::DomainObjectBuilder::buildParticleLayout(const ParticleLayoutItem& item)
-{
-    auto P_layout = GUI::Transform::ToDomain::createParticleLayout(item);
-    QVector<SessionItem*> children = item.getItems();
-    for (int i = 0; i < children.size(); ++i) {
-        auto P_particle = GUI::Transform::ToDomain::createIParticle(*children[i]);
-        if (P_particle) {
-            P_layout->addParticle(*P_particle);
-            continue;
-        }
-
-        throw Error("GUI::Model::DomainObjectBuilder::buildParticleLayout()"
-                    " -> Error! Not implemented");
-    }
-    InterferenceItem* interference = item.interference().currentItem();
-    if (interference) {
-        auto P_interference = interference->createInterference();
-        if (P_interference)
-            P_layout->setInterference(*P_interference);
-    }
-    return P_layout;
-}
diff --git a/GUI/Model/To/DomainObjectBuilder.h b/GUI/Model/To/DomainObjectBuilder.h
deleted file mode 100644
index 132010d55d5..00000000000
--- a/GUI/Model/To/DomainObjectBuilder.h
+++ /dev/null
@@ -1,40 +0,0 @@
-//  ************************************************************************************************
-//
-//  BornAgain: simulate and fit reflection and scattering
-//
-//! @file      GUI/Model/To/DomainObjectBuilder.h
-//! @brief     Defines namespace GUI::Model::DomainObjectBuilder
-//!
-//! @homepage  http://www.bornagainproject.org
-//! @license   GNU General Public License v3 or higher (see COPYING)
-//! @copyright Forschungszentrum Jülich GmbH 2018
-//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
-//
-//  ************************************************************************************************
-
-#ifndef BORNAGAIN_GUI_MODEL_TO_DOMAINOBJECTBUILDER_H
-#define BORNAGAIN_GUI_MODEL_TO_DOMAINOBJECTBUILDER_H
-
-#include <memory>
-
-class MultiLayer;
-class Layer;
-class LayerItem;
-class Instrument;
-class MultiLayerItem;
-class ParticleLayout;
-class ParticleLayoutItem;
-class IInterference;
-class SessionItem;
-class InstrumentItem;
-class ICoordSystem;
-
-namespace GUI::Model::DomainObjectBuilder {
-
-std::unique_ptr<MultiLayer> buildMultiLayer(const MultiLayerItem& item);
-std::unique_ptr<Layer> buildLayer(const LayerItem& item, bool isFirstOrLastLayer);
-std::unique_ptr<ParticleLayout> buildParticleLayout(const ParticleLayoutItem& item);
-
-} // namespace GUI::Model::DomainObjectBuilder
-
-#endif // BORNAGAIN_GUI_MODEL_TO_DOMAINOBJECTBUILDER_H
diff --git a/GUI/Model/To/DomainSimulationBuilder.cpp b/GUI/Model/To/DomainSimulationBuilder.cpp
index 5ee90855d10..8d79baa67c6 100644
--- a/GUI/Model/To/DomainSimulationBuilder.cpp
+++ b/GUI/Model/To/DomainSimulationBuilder.cpp
@@ -30,7 +30,7 @@
 #include "GUI/Model/Instrument/InstrumentItems.h"
 #include "GUI/Model/Sample/MultiLayerItem.h"
 #include "GUI/Model/Session/SimulationOptionsItem.h"
-#include "GUI/Model/To/DomainObjectBuilder.h"
+#include "GUI/Model/To/ToDomain.h"
 #include "GUI/Util/Error.h"
 #include "Param/Distrib/RangedDistributions.h"
 #include "Resample/Options/SimulationOptions.h"
@@ -220,7 +220,7 @@ GUI::Model::DomainSimulationBuilder::createSimulation(const MultiLayerItem* samp
         throw Error(message);
     }
 
-    auto P_multilayer = GUI::Model::DomainObjectBuilder::buildMultiLayer(*sampleItem);
+    auto P_multilayer = GUI::Transform::ToDomain::buildMultiLayer(*sampleItem);
 
     if (const auto* gisasInstrument = dynamic_cast<const GISASInstrumentItem*>(instrumentItem))
         return createGISASSimulation(std::move(P_multilayer), gisasInstrument, optionsItem);
diff --git a/GUI/Model/To/ToDomain.cpp b/GUI/Model/To/ToDomain.cpp
index 9cc724da0ee..3e2ff9eed3e 100644
--- a/GUI/Model/To/ToDomain.cpp
+++ b/GUI/Model/To/ToDomain.cpp
@@ -14,6 +14,7 @@
 
 #include "GUI/Model/To/ToDomain.h"
 #include "GUI/Model/Material/MaterialItem.h"
+#include "GUI/Model/Sample/InterferenceItems.h"
 #include "GUI/Model/Sample/LayerItem.h"
 #include "GUI/Model/Sample/LayerRoughnessItems.h"
 #include "GUI/Model/Sample/MesoCrystalItem.h"
@@ -24,11 +25,20 @@
 #include "GUI/Model/Sample/ParticleLayoutItem.h"
 #include "GUI/Model/Types/DoubleDescriptor.h"
 #include "GUI/Model/Types/UIntDescriptor.h"
+#include "GUI/Util/Error.h"
+#include "Sample/Aggregate/IInterference.h"
+#include "Sample/Aggregate/ParticleLayout.h"
+#include "Sample/Interface/LayerRoughness.h"
+#include "Sample/Multilayer/Layer.h"
+#include "Sample/Multilayer/MultiLayer.h"
+#include "Sample/Particle/IParticle.h"
 #include "Sample/Particle/MesoCrystal.h"
 #include "Sample/Particle/Particle.h"
 #include "Sample/Particle/ParticleCoreShell.h"
 
-std::unique_ptr<MultiLayer> GUI::Transform::ToDomain::createMultiLayer(const MultiLayerItem& item)
+namespace {
+
+std::unique_ptr<MultiLayer> createMultiLayer(const MultiLayerItem& item)
 {
     auto P_multilayer = std::make_unique<MultiLayer>();
     double cross_corr_length = item.crossCorrLength();
@@ -39,8 +49,7 @@ std::unique_ptr<MultiLayer> GUI::Transform::ToDomain::createMultiLayer(const Mul
     return P_multilayer;
 }
 
-std::unique_ptr<Layer> GUI::Transform::ToDomain::createLayer(const LayerItem& item,
-                                                             bool isFirstOrLastLayer)
+std::unique_ptr<Layer> createLayer(const LayerItem& item, bool isFirstOrLastLayer)
 {
     auto P_layer = std::make_unique<Layer>(*item.materialItem()->createMaterial(),
                                            isFirstOrLastLayer ? 0.0 : item.thickness());
@@ -48,22 +57,7 @@ std::unique_ptr<Layer> GUI::Transform::ToDomain::createLayer(const LayerItem& it
     return P_layer;
 }
 
-std::unique_ptr<LayerRoughness> GUI::Transform::ToDomain::createLayerRoughness(
-    const std::variant<LayerZeroRoughnessItem*, LayerBasicRoughnessItem*>& roughness)
-{
-    if (std::holds_alternative<LayerZeroRoughnessItem*>(roughness))
-        return nullptr;
-    if (std::holds_alternative<LayerBasicRoughnessItem*>(roughness)) {
-        const auto& basicRoughnessItem = *std::get<LayerBasicRoughnessItem*>(roughness);
-        return std::make_unique<LayerRoughness>(basicRoughnessItem.sigma(),
-                                                basicRoughnessItem.hurst(),
-                                                basicRoughnessItem.lateralCorrelationLength());
-    }
-    ASSERT(0);
-}
-
-std::unique_ptr<ParticleLayout>
-GUI::Transform::ToDomain::createParticleLayout(const ParticleLayoutItem& item)
+std::unique_ptr<ParticleLayout> createParticleLayout(const ParticleLayoutItem& item)
 {
     auto P_layout = std::make_unique<ParticleLayout>();
     double total_density = item.totalDensity();
@@ -73,7 +67,7 @@ GUI::Transform::ToDomain::createParticleLayout(const ParticleLayoutItem& item)
     return P_layout;
 }
 
-std::unique_ptr<IParticle> GUI::Transform::ToDomain::createIParticle(const SessionItem& item)
+std::unique_ptr<IParticle> createIParticle(const SessionItem& item)
 {
     std::unique_ptr<IParticle> P_particle;
     if (item.hasModelType<ParticleItem>()) {
@@ -91,3 +85,73 @@ std::unique_ptr<IParticle> GUI::Transform::ToDomain::createIParticle(const Sessi
     }
     return P_particle;
 }
+
+std::unique_ptr<ParticleLayout> buildParticleLayout(const ParticleLayoutItem& item)
+{
+    auto P_layout = createParticleLayout(item);
+    QVector<SessionItem*> children = item.getItems();
+    for (int i = 0; i < children.size(); ++i) {
+        auto P_particle = createIParticle(*children[i]);
+        if (P_particle) {
+            P_layout->addParticle(*P_particle);
+            continue;
+        }
+
+        throw Error("GUI::Model::DomainObjectBuilder::buildParticleLayout()"
+                    " -> Error! Not implemented");
+    }
+    InterferenceItem* interference = item.interference().currentItem();
+    if (interference) {
+        auto P_interference = interference->createInterference();
+        if (P_interference)
+            P_layout->setInterference(*P_interference);
+    }
+    return P_layout;
+}
+
+std::unique_ptr<Layer> buildLayer(const LayerItem& item, bool isFirstOrLastLayer)
+{
+    auto P_layer = createLayer(item, isFirstOrLastLayer);
+    for (ParticleLayoutItem* layout : item.layouts()) {
+        auto P_layout = buildParticleLayout(*layout);
+        if (P_layout)
+            P_layer->addLayout(*P_layout);
+    }
+    return P_layer;
+}
+
+} // namespace
+
+std::unique_ptr<LayerRoughness> GUI::Transform::ToDomain::createLayerRoughness(
+    const std::variant<LayerZeroRoughnessItem*, LayerBasicRoughnessItem*>& roughness)
+{
+    if (std::holds_alternative<LayerZeroRoughnessItem*>(roughness))
+        return nullptr;
+    if (std::holds_alternative<LayerBasicRoughnessItem*>(roughness)) {
+        const auto& basicRoughnessItem = *std::get<LayerBasicRoughnessItem*>(roughness);
+        return std::make_unique<LayerRoughness>(basicRoughnessItem.sigma(),
+                                                basicRoughnessItem.hurst(),
+                                                basicRoughnessItem.lateralCorrelationLength());
+    }
+    ASSERT(0);
+}
+
+std::unique_ptr<MultiLayer>
+GUI::Transform::ToDomain::buildMultiLayer(const MultiLayerItem& multiLayerItem)
+{
+    auto P_multilayer = createMultiLayer(multiLayerItem);
+    for (auto* layerItem : multiLayerItem.layers()) {
+        const bool isFirstLayer = layerItem == multiLayerItem.layers().first();
+        const bool isLastLayer = layerItem == multiLayerItem.layers().last();
+        auto P_layer = buildLayer(*layerItem, isFirstLayer || isLastLayer);
+        if (P_layer) {
+            const auto roughness = layerItem->roughness().currentItem();
+            auto P_roughness = createLayerRoughness(roughness);
+            if (P_roughness && !isLastLayer)
+                P_multilayer->addLayerWithTopRoughness(*P_layer, *P_roughness);
+            else
+                P_multilayer->addLayer(*P_layer);
+        }
+    }
+    return P_multilayer;
+}
diff --git a/GUI/Model/To/ToDomain.h b/GUI/Model/To/ToDomain.h
index 3094cc184c4..7fb9eb4225d 100644
--- a/GUI/Model/To/ToDomain.h
+++ b/GUI/Model/To/ToDomain.h
@@ -15,33 +15,23 @@
 #ifndef BORNAGAIN_GUI_MODEL_TO_TODOMAIN_H
 #define BORNAGAIN_GUI_MODEL_TO_TODOMAIN_H
 
-#include "Device/Instrument/Instrument.h"
-#include "Param/Distrib/Distributions.h" // for IDistribution1D
-#include "Param/Distrib/ParameterDistribution.h"
-#include "Sample/Aggregate/IInterference.h"
-#include "Sample/Aggregate/ParticleLayout.h"
-#include "Sample/Interface/LayerRoughness.h"
-#include "Sample/Multilayer/Layer.h"
-#include "Sample/Multilayer/MultiLayer.h"
-#include "Sample/Particle/IParticle.h"
 #include <memory>
 #include <variant>
 
 class LayerItem;
 class MultiLayerItem;
-class ParticleLayoutItem;
-class SessionItem;
+class MultiLayer;
 class LayerZeroRoughnessItem;
 class LayerBasicRoughnessItem;
+class LayerRoughness;
 
 namespace GUI::Transform::ToDomain {
 
-std::unique_ptr<IParticle> createIParticle(const SessionItem& item);
-std::unique_ptr<Layer> createLayer(const LayerItem& item, bool isFirstOrLastLayer);
 std::unique_ptr<LayerRoughness> createLayerRoughness(
     const std::variant<LayerZeroRoughnessItem*, LayerBasicRoughnessItem*>& roughness);
-std::unique_ptr<MultiLayer> createMultiLayer(const MultiLayerItem& item);
-std::unique_ptr<ParticleLayout> createParticleLayout(const ParticleLayoutItem& item);
+
+std::unique_ptr<MultiLayer> buildMultiLayer(const MultiLayerItem& item);
+
 
 } // namespace GUI::Transform::ToDomain
 
diff --git a/GUI/View/SampleDesigner/ScriptPanel.cpp b/GUI/View/SampleDesigner/ScriptPanel.cpp
index 8e3af3eb6b2..6c74ff967d3 100644
--- a/GUI/View/SampleDesigner/ScriptPanel.cpp
+++ b/GUI/View/SampleDesigner/ScriptPanel.cpp
@@ -16,7 +16,7 @@
 #include "Core/Export/ExportToPython.h"
 #include "GUI/Model/Sample/MultiLayerItem.h"
 #include "GUI/Model/Sample/SampleModel.h"
-#include "GUI/Model/To/DomainObjectBuilder.h"
+#include "GUI/Model/To/ToDomain.h"
 #include "GUI/View/Info/CautionSign.h"
 #include "GUI/View/Info/PythonSyntaxHighlighter.h"
 #include "GUI/View/Tool/DesignerHelper.h"
@@ -122,8 +122,7 @@ QString ScriptPanel::generateCodeSnippet()
 
     QString result;
     try {
-        auto multilayer =
-            GUI::Model::DomainObjectBuilder::buildMultiLayer(*m_currentMultiLayerItem);
+        auto multilayer = GUI::Transform::ToDomain::buildMultiLayer(*m_currentMultiLayerItem);
         result.append(QString::fromStdString(Py::Export::sampleCode(*multilayer)));
     } catch (const std::exception& ex) {
         QString message =
diff --git a/Tests/Unit/GUI/TestLayerRoughnessItems.cpp b/Tests/Unit/GUI/TestLayerRoughnessItems.cpp
index 43b7bf204b4..ae1684fa16c 100644
--- a/Tests/Unit/GUI/TestLayerRoughnessItems.cpp
+++ b/Tests/Unit/GUI/TestLayerRoughnessItems.cpp
@@ -2,6 +2,7 @@
 #include "GUI/Model/Sample/LayerRoughnessItems.h"
 #include "GUI/Model/To/ToDomain.h"
 #include "GUI/Model/Types/DoubleDescriptor.h"
+#include "Sample/Interface/LayerRoughness.h"
 #include "Tests/GTestWrapper/google_test.h"
 
 class TestLayerRoughnessItems : public ::testing::Test {
-- 
GitLab


From ae0746d1589a05e9243dfc60d12da98f2f0b9e27 Mon Sep 17 00:00:00 2001
From: Matthias Puchner <github@mpuchner.de>
Date: Thu, 9 Dec 2021 07:23:15 +0100
Subject: [PATCH 05/15] rm SessionItem signaling from MultiLayer (functionality
 changed to signal-free by the last commits)

rm obsolete unit tests
---
 GUI/Model/Job/ParameterTreeUtils.cpp  |  14 ++-
 GUI/Model/Sample/MultiLayerItem.cpp   |  17 ---
 GUI/Model/Sample/MultiLayerItem.h     |   3 -
 Tests/Unit/GUI/TestMapperForItem.cpp  |  19 ----
 Tests/Unit/GUI/TestMultiLayerItem.cpp | 144 --------------------------
 5 files changed, 13 insertions(+), 184 deletions(-)
 delete mode 100644 Tests/Unit/GUI/TestMultiLayerItem.cpp

diff --git a/GUI/Model/Job/ParameterTreeUtils.cpp b/GUI/Model/Job/ParameterTreeUtils.cpp
index 94c00843153..eb0d2a7de90 100644
--- a/GUI/Model/Job/ParameterTreeUtils.cpp
+++ b/GUI/Model/Job/ParameterTreeUtils.cpp
@@ -21,6 +21,7 @@
 #include "GUI/Model/Material/MaterialItem.h"
 #include "GUI/Model/Sample/InterferenceItems.h"
 #include "GUI/Model/Sample/Lattice2DItems.h"
+#include "GUI/Model/Sample/LayerItem.h"
 #include "GUI/Model/Sample/MultiLayerItem.h"
 #include "GUI/Model/Sample/ParticleCoreShellItem.h"
 #include "GUI/Model/Sample/ParticleItem.h"
@@ -134,7 +135,18 @@ void GUI::Model::ParameterTreeUtils::createParameterTree(JobItem* jobItem,
         }
     }
 
-    // add sample
+    // add sample.
+    // #baMigration To ignore thickness/roughness, they are disabled. This has to be changed after
+    // SessionModel migration. Compare also to handling in
+    // LayerForm::updateLayerPositionDependentElements()
+    for (auto* layer : jobItem->sampleItem()->layers()) {
+        const bool isFirstLayer = jobItem->sampleItem()->layers().first() == layer;
+        const bool isLastLayer = jobItem->sampleItem()->layers().last() == layer;
+
+        layer->setRoughnessEnabled(!isFirstLayer);
+        layer->setThicknessEnabled(!isFirstLayer && !isLastLayer);
+    }
+
     populateParameterContainer(container, jobItem->sampleItem(), recreateBackupValues);
 
     // add instrument
diff --git a/GUI/Model/Sample/MultiLayerItem.cpp b/GUI/Model/Sample/MultiLayerItem.cpp
index 5b783ba1e81..f4efbd14577 100644
--- a/GUI/Model/Sample/MultiLayerItem.cpp
+++ b/GUI/Model/Sample/MultiLayerItem.cpp
@@ -36,8 +36,6 @@ MultiLayerItem::MultiLayerItem() : SessionItem(M_TYPE)
 
     registerTag(T_LAYERS, 0, -1, {LayerItem::M_TYPE});
     setDefaultTag(T_LAYERS);
-
-    mapper()->setOnChildrenChange([this](SessionItem*) { updateLayers(); });
 }
 
 QVector<ItemWithMaterial*> MultiLayerItem::itemsWithMaterial() const
@@ -120,7 +118,6 @@ void MultiLayerItem::removeLayer(LayerItem* item)
     model()->removeItem(item);
 }
 
-
 void MultiLayerItem::moveLayer(LayerItem* item, LayerItem* beforeThisLayer)
 {
     int index = -1; // move to end
@@ -129,17 +126,3 @@ void MultiLayerItem::moveLayer(LayerItem* item, LayerItem* beforeThisLayer)
 
     model()->moveItem(item, this, index);
 }
-
-void MultiLayerItem::updateLayers()
-{
-    QVector<LayerItem*> list = childrenOfType<LayerItem>();
-    for (auto* it = list.begin(); it != list.end(); ++it) {
-        (*it)->setRoughnessEnabled(it != list.begin());
-
-        if (it == list.begin() || it == (list.end() - 1)) {
-            (*it)->setThicknessEnabled(false);
-            (*it)->thickness().set(0.0);
-        } else
-            (*it)->setThicknessEnabled(true);
-    }
-}
diff --git a/GUI/Model/Sample/MultiLayerItem.h b/GUI/Model/Sample/MultiLayerItem.h
index b5e42623853..e89ec57a230 100644
--- a/GUI/Model/Sample/MultiLayerItem.h
+++ b/GUI/Model/Sample/MultiLayerItem.h
@@ -62,9 +62,6 @@ public:
 
     void removeLayer(LayerItem* item);
     void moveLayer(LayerItem* item, LayerItem* beforeThisLayer);
-
-private:
-    void updateLayers();
 };
 
 #endif // BORNAGAIN_GUI_MODEL_SAMPLE_MULTILAYERITEM_H
diff --git a/Tests/Unit/GUI/TestMapperForItem.cpp b/Tests/Unit/GUI/TestMapperForItem.cpp
index e5178cd382c..174881cb6ca 100644
--- a/Tests/Unit/GUI/TestMapperForItem.cpp
+++ b/Tests/Unit/GUI/TestMapperForItem.cpp
@@ -176,25 +176,6 @@ TEST_F(TestMapperForItem, onPropertyChange)
                 && MultiLayerItem::isCrossCorrLengthPropertyName(w.m_reported_names[0]));
 }
 
-TEST_F(TestMapperForItem, onChildrenChange)
-{
-    Widget w;
-    SampleModel model;
-    auto* multilayer = model.insertItem<MultiLayerItem>();
-
-    // Mapper is looking on parent; adding new child to parent
-    setItem(multilayer, &w);
-    EXPECT_TRUE(m_mapped_item == multilayer);
-    model.insertItem<LayerItem>(multilayer);
-
-    EXPECT_EQ(w.m_onPropertyChangeCount, 0);
-    EXPECT_EQ(w.m_onChildPropertyChangeCount, 2);
-    EXPECT_EQ(w.m_onParentChangeCount, 0);
-    EXPECT_EQ(w.m_onChildrenChangeCount, 1);
-    EXPECT_EQ(w.m_reported_items.size(), 2);
-    EXPECT_EQ(w.m_reported_names.size(), 2);
-}
-
 TEST_F(TestMapperForItem, onSiblingsChange)
 {
     Widget w;
diff --git a/Tests/Unit/GUI/TestMultiLayerItem.cpp b/Tests/Unit/GUI/TestMultiLayerItem.cpp
deleted file mode 100644
index bf2f2bd60cb..00000000000
--- a/Tests/Unit/GUI/TestMultiLayerItem.cpp
+++ /dev/null
@@ -1,144 +0,0 @@
-#include "GUI/Model/Sample/LayerItem.h"
-#include "GUI/Model/Sample/LayerRoughnessItems.h"
-#include "GUI/Model/Sample/MultiLayerItem.h"
-#include "GUI/Model/Sample/SampleModel.h"
-#include "GUI/Model/Types/DoubleDescriptor.h"
-#include "Tests/GTestWrapper/google_test.h"
-
-class TestMultiLayerItem : public ::testing::Test {
-};
-
-//! Testing layer appearance (enabled, disabled) in a MultiLayer made of two default layers.
-//!
-//! In two layer system top and bottom layers should have disabled thickness and roughness.
-
-TEST_F(TestMultiLayerItem, twoLayerSystem)
-{
-    SampleModel model;
-
-    auto* multilayer = model.addMultiLayer();
-    auto* top = multilayer->addLayer();
-    auto* bottom = multilayer->addLayer();
-
-    // Thickness property should be disabled for top and bottom layers
-    EXPECT_FALSE(top->thicknessItem()->isEnabled());
-    EXPECT_FALSE(bottom->thicknessItem()->isEnabled());
-
-    // Thickness value should be 0.0 for top and bottom layers
-    EXPECT_EQ(top->thickness(), 0.0);
-    EXPECT_EQ(bottom->thickness(), 0.0);
-
-    // Roughness group property should be disabled for top, and enabled for bottom layers
-    EXPECT_FALSE(top->roughnessItem()->isEnabled());
-    EXPECT_TRUE(bottom->roughnessItem()->isEnabled());
-
-    // Default roughness should be "LayerZeroRoughness"
-    EXPECT_TRUE(std::holds_alternative<LayerZeroRoughnessItem*>(top->roughness().currentItem()));
-    EXPECT_TRUE(std::holds_alternative<LayerZeroRoughnessItem*>(bottom->roughness().currentItem()));
-}
-
-//! Testing layer appearance (enabled, disabled) in a MultiLayer made of three default layers.
-//!
-//! In three layer system middle layer's thickness/roughness should be enabled.
-
-TEST_F(TestMultiLayerItem, threeLayerSystem)
-{
-    SampleModel model;
-
-    auto* multilayer = model.addMultiLayer();
-    auto* top = multilayer->addLayer();
-    auto* middle = multilayer->addLayer();
-    auto* bottom = multilayer->addLayer();
-
-    // Thickness property should be disabled for top and bottom layers and enabled for middle
-    EXPECT_FALSE(top->thicknessItem()->isEnabled());
-    EXPECT_TRUE(middle->thicknessItem()->isEnabled());
-    EXPECT_FALSE(bottom->thicknessItem()->isEnabled());
-
-    // Thickness value should be 0.0 for top and bottom layers
-    EXPECT_EQ(top->thickness(), 0.0);
-    EXPECT_EQ(middle->thickness(), 0.0);
-    EXPECT_EQ(bottom->thickness(), 0.0);
-
-    // Roughness group property should be disabled for top, and enabled for other layers
-    EXPECT_FALSE(top->roughnessItem()->isEnabled());
-    EXPECT_TRUE(middle->roughnessItem()->isEnabled());
-    EXPECT_TRUE(bottom->roughnessItem()->isEnabled());
-
-    // Default roughness should be "LayerZeroRoughness"
-    EXPECT_TRUE(std::holds_alternative<LayerZeroRoughnessItem*>(top->roughness().currentItem()));
-    EXPECT_TRUE(std::holds_alternative<LayerZeroRoughnessItem*>(middle->roughness().currentItem()));
-    EXPECT_TRUE(std::holds_alternative<LayerZeroRoughnessItem*>(bottom->roughness().currentItem()));
-}
-
-//! Testing middle layer appearance when it is moved to the top.
-//!
-//! In three layer system, the moving of middle layer on top should lead to the disabling
-//! of roughness/thickness.
-
-TEST_F(TestMultiLayerItem, movingMiddleLayerOnTop)
-{
-    SampleModel model;
-
-    auto* multilayer = model.addMultiLayer();
-    auto* top = multilayer->addLayer();
-    auto* middle = multilayer->addLayer();
-    auto* bottom = multilayer->addLayer();
-
-    const double thickness = 10.0;
-    middle->thickness().set(thickness);
-
-    // Thickness property should be disabled for top and bottom layers and enabled for middle
-    EXPECT_FALSE(top->thicknessItem()->isEnabled());
-    EXPECT_TRUE(middle->thicknessItem()->isEnabled());
-    EXPECT_FALSE(bottom->thicknessItem()->isEnabled());
-
-    // Roughness group property should be disabled for top, and enabled for other layers
-    EXPECT_FALSE(top->roughnessItem()->isEnabled());
-    EXPECT_TRUE(middle->roughnessItem()->isEnabled());
-    EXPECT_TRUE(bottom->roughnessItem()->isEnabled());
-
-    // Thickness value should be 0.0 for top and bottom layers
-    EXPECT_EQ(top->thickness(), 0.0);
-    EXPECT_EQ(middle->thickness(), thickness);
-    EXPECT_EQ(bottom->thickness(), 0.0);
-
-    // Moving middle layer to top
-    multilayer->moveLayer(middle, top);
-    // checking that middle is top now, and top layer is middle now
-    EXPECT_EQ(middle, multilayer->layers().at(0));
-    EXPECT_EQ(top, multilayer->layers().at(1));
-
-    // Thickness and roughness of middle layer should be disabled now
-    EXPECT_FALSE(middle->thicknessItem()->isEnabled());
-    EXPECT_FALSE(middle->roughnessItem()->isEnabled());
-    // And, thickness of middle should become 0 to stress the fact that it become top
-    EXPECT_EQ(middle->thickness(), 0.0);
-
-    // Thickness and roughness of former top layer should be enabled now, since it is in the middle
-    EXPECT_TRUE(top->thicknessItem()->isEnabled());
-    EXPECT_TRUE(top->roughnessItem()->isEnabled());
-}
-
-//! Testing layer appearance when it is moved from a MultiLayer to canvas.
-//!
-//! If top layer was moved to canvas, its thickness and roughness should be reenabled.
-
-TEST_F(TestMultiLayerItem, movingLayerOnCanvas)
-{
-    SampleModel model;
-
-    auto* multilayer = model.addMultiLayer();
-    auto* top = model.insertItem<LayerItem>(multilayer);
-    model.insertItem<LayerItem>(multilayer);
-
-    // Moving top layer to canvas
-    model.moveItem(top, nullptr);
-
-    // checking that it was moved
-    EXPECT_EQ(top->parent(), model.rootItem());
-
-    // thickness should be reenabled
-    //    EXPECT_TRUE(top->getItem(LayerItem::P_THICKNESS)->isEnabled());
-    //    EXPECT_TRUE(top->getItem(LayerItem::P_THICKNESS)->isEnabled());
-}
-- 
GitLab


From 271cc41526e4bf5c3d15bda2a890f334abdb4e28 Mon Sep 17 00:00:00 2001
From: Matthias Puchner <github@mpuchner.de>
Date: Thu, 9 Dec 2021 07:16:10 +0100
Subject: [PATCH 06/15] reduce SessionItem usage, reduce SessionItem signaling

---
 GUI/Model/Job/ParameterTreeUtils.cpp          |  5 ++
 GUI/Model/Sample/ParticleLayoutItem.cpp       | 49 +++++--------------
 GUI/Model/Sample/ParticleLayoutItem.h         | 10 +++-
 .../SampleDesigner/ParticleLayoutForm.cpp     | 10 +---
 .../SampleDesigner/SampleEditorController.cpp |  4 +-
 5 files changed, 30 insertions(+), 48 deletions(-)

diff --git a/GUI/Model/Job/ParameterTreeUtils.cpp b/GUI/Model/Job/ParameterTreeUtils.cpp
index eb0d2a7de90..1363a6f03d0 100644
--- a/GUI/Model/Job/ParameterTreeUtils.cpp
+++ b/GUI/Model/Job/ParameterTreeUtils.cpp
@@ -25,6 +25,7 @@
 #include "GUI/Model/Sample/MultiLayerItem.h"
 #include "GUI/Model/Sample/ParticleCoreShellItem.h"
 #include "GUI/Model/Sample/ParticleItem.h"
+#include "GUI/Model/Sample/ParticleLayoutItem.h"
 #include "GUI/Model/Session/ModelPath.h"
 #include "GUI/Model/Types/VectorDescriptor.h"
 #include "GUI/Util/Error.h"
@@ -139,12 +140,16 @@ void GUI::Model::ParameterTreeUtils::createParameterTree(JobItem* jobItem,
     // #baMigration To ignore thickness/roughness, they are disabled. This has to be changed after
     // SessionModel migration. Compare also to handling in
     // LayerForm::updateLayerPositionDependentElements()
+    // dto. for totalDensity of particle layout
     for (auto* layer : jobItem->sampleItem()->layers()) {
         const bool isFirstLayer = jobItem->sampleItem()->layers().first() == layer;
         const bool isLastLayer = jobItem->sampleItem()->layers().last() == layer;
 
         layer->setRoughnessEnabled(!isFirstLayer);
         layer->setThicknessEnabled(!isFirstLayer && !isLastLayer);
+
+        for (auto* layout : layer->layouts())
+            layout->enableDensity(!layout->totalDensityIsDefinedByInterference());
     }
 
     populateParameterContainer(container, jobItem->sampleItem(), recreateBackupValues);
diff --git a/GUI/Model/Sample/ParticleLayoutItem.cpp b/GUI/Model/Sample/ParticleLayoutItem.cpp
index e86e9254883..5337931b463 100644
--- a/GUI/Model/Sample/ParticleLayoutItem.cpp
+++ b/GUI/Model/Sample/ParticleLayoutItem.cpp
@@ -23,21 +23,6 @@
 
 namespace {
 
-//! Returns true if name is related to 2D interference functions.
-bool isInterference2D(const QString& model_type)
-{
-    return model_type == Interference2DLatticeItem::M_TYPE
-           || model_type == Interference2DParaCrystalItem::M_TYPE
-           || model_type == InterferenceFinite2DLatticeItem::M_TYPE
-           || model_type == InterferenceHardDiskItem::M_TYPE;
-}
-
-//! Returns true if name is related to 2D interference functions.
-bool isLattice2D(SessionItem* item)
-{
-    return dynamic_cast<Lattice2DItem*>(item);
-}
-
 const QString density_tooltip =
     "Number of particles per square nanometer (particle surface density).\n "
     "Should be defined for disordered and 1d-ordered particle collections.";
@@ -64,15 +49,7 @@ ParticleLayoutItem::ParticleLayoutItem() : SessionItem(M_TYPE)
                  Interference2DParaCrystalItem::M_TYPE, InterferenceFinite2DLatticeItem::M_TYPE,
                  InterferenceHardDiskItem::M_TYPE, InterferenceRadialParaCrystalItem::M_TYPE});
 
-    mapper()->setOnChildrenChange([this](SessionItem*) {
-        updateDensityAppearance();
-        updateDensityValue();
-    });
-
-    mapper()->setOnAnyChildChange([this](SessionItem* item) {
-        if (isLattice2D(item) || (item && isLattice2D(item->parent())))
-            updateDensityValue();
-    });
+    mapper()->setOnAnyChildChange([this](SessionItem*) { updateDensityValue(); });
 }
 
 DoubleDescriptor ParticleLayoutItem::totalDensity() const
@@ -157,18 +134,6 @@ void ParticleLayoutItem::removeInterference()
         model()->removeItem(item);
 }
 
-//! Disables/enables total density property, depending on type of interference function.
-//! Two dimensional interference calculates density automatically, so property should
-//! be disabled.
-
-void ParticleLayoutItem::updateDensityAppearance()
-{
-    getItem(P_TOTAL_DENSITY)->setEnabled(true);
-    if (auto* interferenceItem = getItem(T_INTERFERENCE))
-        if (isInterference2D(interferenceItem->modelType()))
-            getItem(P_TOTAL_DENSITY)->setEnabled(false);
-}
-
 //! Updates the value of TotalSurfaceDensity on lattice type change.
 
 void ParticleLayoutItem::updateDensityValue()
@@ -190,3 +155,15 @@ void ParticleLayoutItem::updateDensityValue()
         }
     }
 }
+
+void ParticleLayoutItem::enableDensity(bool b)
+{
+    getItem(P_TOTAL_DENSITY)->setEnabled(b);
+}
+
+bool ParticleLayoutItem::totalDensityIsDefinedByInterference() const
+{
+    const auto* interferenceItem = getItem(T_INTERFERENCE);
+    return dynamic_cast<const Interference2DAbstractLatticeItem*>(interferenceItem)
+           || dynamic_cast<const InterferenceHardDiskItem*>(interferenceItem);
+}
diff --git a/GUI/Model/Sample/ParticleLayoutItem.h b/GUI/Model/Sample/ParticleLayoutItem.h
index 911b01e763d..45dcd29bb0c 100644
--- a/GUI/Model/Sample/ParticleLayoutItem.h
+++ b/GUI/Model/Sample/ParticleLayoutItem.h
@@ -48,8 +48,14 @@ public:
 
     void updateDensityValue();
 
-private:
-    void updateDensityAppearance();
+    // #baMigration Use only while not migrated to SessionModel!
+    void enableDensity(bool b);
+
+    //! Returns whether total density is defined by the currently selected interference.
+    //!
+    //! Two dimensional interference calculates density automatically; in these cases the "own"
+    //! total density value should not be edited but set by the one from the interference.
+    bool totalDensityIsDefinedByInterference() const;
 };
 
 template <typename T> T* ParticleLayoutItem::createInterference()
diff --git a/GUI/View/SampleDesigner/ParticleLayoutForm.cpp b/GUI/View/SampleDesigner/ParticleLayoutForm.cpp
index 6ee952899de..4b3e091411b 100644
--- a/GUI/View/SampleDesigner/ParticleLayoutForm.cpp
+++ b/GUI/View/SampleDesigner/ParticleLayoutForm.cpp
@@ -100,15 +100,7 @@ void ParticleLayoutForm::onAboutToRemoveParticle(ItemWithParticles* item)
 
 void ParticleLayoutForm::updateDensityEnabling()
 {
-    const auto* currentInterference = m_layoutItem->interference().currentItem();
-    const bool enableTotalDensityInParticleLayout =
-        currentInterference == nullptr
-        || (!dynamic_cast<const Interference2DLatticeItem*>(currentInterference)
-            && !dynamic_cast<const Interference2DParaCrystalItem*>(currentInterference)
-            && !dynamic_cast<const InterferenceFinite2DLatticeItem*>(currentInterference)
-            && !dynamic_cast<const InterferenceHardDiskItem*>(currentInterference));
-
-    m_totalDensitySpinBox->setEnabled(enableTotalDensityInParticleLayout);
+    m_totalDensitySpinBox->setEnabled(!m_layoutItem->totalDensityIsDefinedByInterference());
 }
 
 void ParticleLayoutForm::updateDensityValue()
diff --git a/GUI/View/SampleDesigner/SampleEditorController.cpp b/GUI/View/SampleDesigner/SampleEditorController.cpp
index 8515bf3e0c9..d26d2dbb0a3 100644
--- a/GUI/View/SampleDesigner/SampleEditorController.cpp
+++ b/GUI/View/SampleDesigner/SampleEditorController.cpp
@@ -421,8 +421,10 @@ void SampleEditorController::selectInterference(InterferenceForm* widget, int ne
     while (parent != nullptr && dynamic_cast<ParticleLayoutForm*>(parent) == nullptr)
         parent = parent->parentWidget();
 
-    if (auto* particleLayoutForm = dynamic_cast<ParticleLayoutForm*>(parent))
+    if (auto* particleLayoutForm = dynamic_cast<ParticleLayoutForm*>(parent)) {
         particleLayoutForm->updateDensityEnabling();
+        particleLayoutForm->layoutItem()->updateDensityValue();
+    }
 }
 
 void SampleEditorController::setIntegrateOverXi(LatticeTypeSelectionForm* widget, bool newValue)
-- 
GitLab


From f831e92d237956b126bb4cddcf0067e352cc3d49 Mon Sep 17 00:00:00 2001
From: Matthias Puchner <github@mpuchner.de>
Date: Thu, 11 Nov 2021 15:54:32 +0100
Subject: [PATCH 07/15] free ParticleLayoutItem from SessionItem signaling

---
 GUI/Model/From/GUISampleBuilder.cpp           |  2 +-
 GUI/Model/Sample/ParticleLayoutItem.cpp       | 54 ++++++++++---------
 GUI/Model/Sample/ParticleLayoutItem.h         | 16 ++++--
 .../SampleDesigner/ParticleLayoutForm.cpp     |  8 ++-
 .../SampleDesigner/SampleEditorController.cpp |  3 +-
 5 files changed, 50 insertions(+), 33 deletions(-)

diff --git a/GUI/Model/From/GUISampleBuilder.cpp b/GUI/Model/From/GUISampleBuilder.cpp
index 56cd8f585dc..3c589f04920 100644
--- a/GUI/Model/From/GUISampleBuilder.cpp
+++ b/GUI/Model/From/GUISampleBuilder.cpp
@@ -68,7 +68,7 @@ MultiLayerItem* GUISampleBuilder::populateSampleModel(SampleModel* sampleModel,
         // iterate over particle layouts
         for (const auto* layout : layer->layouts()) {
             auto* layoutItem = layerItem->addLayout();
-            layoutItem->totalDensity().set(layout->totalParticleSurfaceDensity());
+            layoutItem->ownDensity().set(layout->totalParticleSurfaceDensity());
             layoutItem->weight().set(layout->weight());
             FromDomain::setInterference(layoutItem, layout->interferenceFunction());
 
diff --git a/GUI/Model/Sample/ParticleLayoutItem.cpp b/GUI/Model/Sample/ParticleLayoutItem.cpp
index 5337931b463..413e99a4c1e 100644
--- a/GUI/Model/Sample/ParticleLayoutItem.cpp
+++ b/GUI/Model/Sample/ParticleLayoutItem.cpp
@@ -48,11 +48,9 @@ ParticleLayoutItem::ParticleLayoutItem() : SessionItem(M_TYPE)
                 {Interference1DLatticeItem::M_TYPE, Interference2DLatticeItem::M_TYPE,
                  Interference2DParaCrystalItem::M_TYPE, InterferenceFinite2DLatticeItem::M_TYPE,
                  InterferenceHardDiskItem::M_TYPE, InterferenceRadialParaCrystalItem::M_TYPE});
-
-    mapper()->setOnAnyChildChange([this](SessionItem*) { updateDensityValue(); });
 }
 
-DoubleDescriptor ParticleLayoutItem::totalDensity() const
+DoubleDescriptor ParticleLayoutItem::ownDensity() const
 {
     DoubleDescriptor d(getItem(P_TOTAL_DENSITY), Unit::nanometerPowerMinus2);
     d.tooltip = "Number of particles per area (particle surface density).\n "
@@ -60,6 +58,34 @@ DoubleDescriptor ParticleLayoutItem::totalDensity() const
     return d;
 }
 
+double ParticleLayoutItem::totalDensity() const
+{
+    if (!totalDensityIsDefinedByInterference())
+        return ownDensity();
+
+    const auto* interferenceItem = getItem(T_INTERFERENCE);
+    ASSERT(interferenceItem);
+
+    if (const auto* interLatticeItem =
+            dynamic_cast<const Interference2DAbstractLatticeItem*>(interferenceItem)) {
+        Lattice2DItem* latticeItem = interLatticeItem->latticeType().currentItem();
+        try {
+            const double area = latticeItem->unitCellArea();
+            return area == 0.0 ? 0.0 : 1.0 / area;
+        } catch (const std::exception&) {
+            // nothing to do here; new exception will be caught during job execution
+            return 0.0;
+        }
+    } else if (const auto* interHDItem =
+                   dynamic_cast<const InterferenceHardDiskItem*>(interferenceItem))
+        return interHDItem->density();
+    else {
+        ASSERT(false);
+        return 0.0;
+    }
+}
+
+
 DoubleDescriptor ParticleLayoutItem::weight() const
 {
     return DoubleDescriptor(getItem(P_WEIGHT), Unit::unitless);
@@ -134,28 +160,6 @@ void ParticleLayoutItem::removeInterference()
         model()->removeItem(item);
 }
 
-//! Updates the value of TotalSurfaceDensity on lattice type change.
-
-void ParticleLayoutItem::updateDensityValue()
-{
-    if (auto* interferenceItem = getItem(T_INTERFERENCE)) {
-        if (auto* interLatticeItem =
-                dynamic_cast<Interference2DAbstractLatticeItem*>(interferenceItem)) {
-            Lattice2DItem* latticeItem = interLatticeItem->latticeType().currentItem();
-            double area = 0.0;
-            try {
-                area = latticeItem->unitCellArea();
-            } catch (const std::exception&) {
-                // nothing to do here; new exception will be caught during job execution
-            }
-            setItemValue(P_TOTAL_DENSITY, area == 0.0 ? 0.0 : 1.0 / area);
-        } else if (auto* interHDItem = dynamic_cast<InterferenceHardDiskItem*>(interferenceItem)) {
-            double density = interHDItem->density();
-            setItemValue(P_TOTAL_DENSITY, density);
-        }
-    }
-}
-
 void ParticleLayoutItem::enableDensity(bool b)
 {
     getItem(P_TOTAL_DENSITY)->setEnabled(b);
diff --git a/GUI/Model/Sample/ParticleLayoutItem.h b/GUI/Model/Sample/ParticleLayoutItem.h
index 45dcd29bb0c..95a1b26e47d 100644
--- a/GUI/Model/Sample/ParticleLayoutItem.h
+++ b/GUI/Model/Sample/ParticleLayoutItem.h
@@ -34,7 +34,19 @@ public:
 
     ParticleLayoutItem();
 
-    DoubleDescriptor totalDensity() const;
+    //! The density value which belonging only to the layout.
+    //!
+    //! This is the editable value. If an interference is present, this value may not be the one to
+    //! be used for building the domain layout or to be presented. For the correct value-to-use,
+    //! whether an interference is present or not, use totalDensity().
+    DoubleDescriptor ownDensity() const;
+
+    //! The real density.
+    //!
+    //! Returns ownDensity() if the interference is not influencing the density. If the
+    //! interference is defining the density, this is the interference-calculated density.
+    double totalDensity() const;
+
     DoubleDescriptor weight() const;
 
     QVector<ItemWithParticles*> particles() const;
@@ -46,8 +58,6 @@ public:
     void setInterference(InterferenceItem* interference);
     void removeInterference();
 
-    void updateDensityValue();
-
     // #baMigration Use only while not migrated to SessionModel!
     void enableDensity(bool b);
 
diff --git a/GUI/View/SampleDesigner/ParticleLayoutForm.cpp b/GUI/View/SampleDesigner/ParticleLayoutForm.cpp
index 4b3e091411b..c774df6e970 100644
--- a/GUI/View/SampleDesigner/ParticleLayoutForm.cpp
+++ b/GUI/View/SampleDesigner/ParticleLayoutForm.cpp
@@ -34,7 +34,7 @@ ParticleLayoutForm::ParticleLayoutForm(LayerForm* parent, ParticleLayoutItem* la
 {
     FormLayouter layouter(this, ec);
     layouter.setContentsMargins(30, 6, 0, 0);
-    int rowOfTotalDensity = layouter.addValue(m_layoutItem->totalDensity());
+    int rowOfTotalDensity = layouter.addValue(m_layoutItem->ownDensity());
     m_totalDensitySpinBox =
         layouter.widgetAt<DoubleSpinBox*>(rowOfTotalDensity, QFormLayout::FieldRole);
     ASSERT(m_totalDensitySpinBox);
@@ -105,7 +105,11 @@ void ParticleLayoutForm::updateDensityEnabling()
 
 void ParticleLayoutForm::updateDensityValue()
 {
-    m_totalDensitySpinBox->updateValue();
+    if (m_layoutItem->totalDensityIsDefinedByInterference()) {
+        QSignalBlocker b(m_totalDensitySpinBox);
+        m_totalDensitySpinBox->setBaseValue(m_layoutItem->totalDensity());
+    } else
+        m_totalDensitySpinBox->updateValue();
 }
 
 void ParticleLayoutForm::updateTitle(const LayerItem* layerItem)
diff --git a/GUI/View/SampleDesigner/SampleEditorController.cpp b/GUI/View/SampleDesigner/SampleEditorController.cpp
index d26d2dbb0a3..7648d6e4fb5 100644
--- a/GUI/View/SampleDesigner/SampleEditorController.cpp
+++ b/GUI/View/SampleDesigner/SampleEditorController.cpp
@@ -348,7 +348,6 @@ void SampleEditorController::setDensityRelatedValue(InterferenceItem* interferen
     auto* particlelayoutItem = dynamic_cast<ParticleLayoutItem*>(interferenceItem->parent());
     if (!particlelayoutItem)
         return;
-    particlelayoutItem->updateDensityValue();
 
     // -- notify the containing particle layout UI about changed value
     ASSERT(m_multiLayerForm);
@@ -423,7 +422,7 @@ void SampleEditorController::selectInterference(InterferenceForm* widget, int ne
 
     if (auto* particleLayoutForm = dynamic_cast<ParticleLayoutForm*>(parent)) {
         particleLayoutForm->updateDensityEnabling();
-        particleLayoutForm->layoutItem()->updateDensityValue();
+        particleLayoutForm->updateDensityValue();
     }
 }
 
-- 
GitLab


From e021c014219152c96dd9ca738d8442572873d00c Mon Sep 17 00:00:00 2001
From: Matthias Puchner <github@mpuchner.de>
Date: Thu, 9 Dec 2021 07:55:52 +0100
Subject: [PATCH 08/15] cleanup

---
 GUI/Model/Sample/ParticleLayoutItem.cpp | 21 ++++++---------------
 1 file changed, 6 insertions(+), 15 deletions(-)

diff --git a/GUI/Model/Sample/ParticleLayoutItem.cpp b/GUI/Model/Sample/ParticleLayoutItem.cpp
index 413e99a4c1e..704f166e372 100644
--- a/GUI/Model/Sample/ParticleLayoutItem.cpp
+++ b/GUI/Model/Sample/ParticleLayoutItem.cpp
@@ -21,24 +21,13 @@
 #include "GUI/Model/Sample/ParticleItem.h"
 #include "GUI/Model/Types/DoubleDescriptor.h"
 
-namespace {
-
-const QString density_tooltip =
-    "Number of particles per square nanometer (particle surface density).\n "
-    "Should be defined for disordered and 1d-ordered particle collections.";
-const QString weight_tooltip =
-    "Weight of this particle layout.\n"
-    "Should be used when multiple layouts define different domains in the sample.";
-
-} // namespace
-
 ParticleLayoutItem::ParticleLayoutItem() : SessionItem(M_TYPE)
 {
     setToolTip("A layout of particles");
 
-    addProperty(P_TOTAL_DENSITY, 0.01)->setToolTip(density_tooltip);
+    addProperty(P_TOTAL_DENSITY, 0.01);
     getItem(P_TOTAL_DENSITY)->setDecimals(10);
-    addProperty(P_WEIGHT, 1.0)->setToolTip(weight_tooltip);
+    addProperty(P_WEIGHT, 1.0);
 
     registerTag(T_PARTICLES, 0, -1,
                 {ParticleItem::M_TYPE, ParticleCoreShellItem::M_TYPE,
@@ -85,10 +74,12 @@ double ParticleLayoutItem::totalDensity() const
     }
 }
 
-
 DoubleDescriptor ParticleLayoutItem::weight() const
 {
-    return DoubleDescriptor(getItem(P_WEIGHT), Unit::unitless);
+    DoubleDescriptor d(getItem(P_WEIGHT), Unit::unitless);
+    d.tooltip = "Weight of this particle layout.\n"
+                "Should be used when multiple layouts define different domains in the sample.";
+    return d;
 }
 
 QVector<ItemWithParticles*> ParticleLayoutItem::particles() const
-- 
GitLab


From 74940f034ad9537bdef1424e03820f30ba98ebfb Mon Sep 17 00:00:00 2001
From: Matthias Puchner <github@mpuchner.de>
Date: Thu, 9 Dec 2021 08:14:54 +0100
Subject: [PATCH 09/15] free InterferenceItems from SessionItem signaling

---
 GUI/Model/Job/ParameterTreeUtils.cpp   | 21 +++++++++++++++++-
 GUI/Model/Sample/InterferenceItems.cpp | 30 --------------------------
 GUI/Model/Sample/InterferenceItems.h   |  3 ---
 3 files changed, 20 insertions(+), 34 deletions(-)

diff --git a/GUI/Model/Job/ParameterTreeUtils.cpp b/GUI/Model/Job/ParameterTreeUtils.cpp
index 1363a6f03d0..d5f095b9948 100644
--- a/GUI/Model/Job/ParameterTreeUtils.cpp
+++ b/GUI/Model/Job/ParameterTreeUtils.cpp
@@ -19,6 +19,7 @@
 #include "GUI/Model/Instrument/InstrumentItems.h"
 #include "GUI/Model/Job/JobItem.h"
 #include "GUI/Model/Material/MaterialItem.h"
+#include "GUI/Model/Sample/FTDistributionItems.h"
 #include "GUI/Model/Sample/InterferenceItems.h"
 #include "GUI/Model/Sample/Lattice2DItems.h"
 #include "GUI/Model/Sample/LayerItem.h"
@@ -148,8 +149,26 @@ void GUI::Model::ParameterTreeUtils::createParameterTree(JobItem* jobItem,
         layer->setRoughnessEnabled(!isFirstLayer);
         layer->setThicknessEnabled(!isFirstLayer && !isLastLayer);
 
-        for (auto* layout : layer->layouts())
+        for (auto* layout : layer->layouts()) {
             layout->enableDensity(!layout->totalDensityIsDefinedByInterference());
+
+            // if PDF1 and PDF2 are of same type, add a number to the name
+            if (auto* itf = dynamic_cast<Interference2DParaCrystalItem*>(
+                    layout->interference().currentItem())) {
+                SessionItem* pdf1 = itf->probabilityDistribution1().currentItem();
+                SessionItem* pdf2 = itf->probabilityDistribution2().currentItem();
+
+                if (pdf1 && pdf2) {
+                    if (pdf1->modelType() == pdf2->modelType()) {
+                        pdf1->setDisplayName(pdf1->modelType() + " 0");
+                        pdf2->setDisplayName(pdf2->modelType() + " 1");
+                    } else {
+                        pdf1->setDisplayName(pdf1->modelType());
+                        pdf2->setDisplayName(pdf2->modelType());
+                    }
+                }
+            }
+        }
     }
 
     populateParameterContainer(container, jobItem->sampleItem(), recreateBackupValues);
diff --git a/GUI/Model/Sample/InterferenceItems.cpp b/GUI/Model/Sample/InterferenceItems.cpp
index 54d34ea8fd9..c9f3e65a3b7 100644
--- a/GUI/Model/Sample/InterferenceItems.cpp
+++ b/GUI/Model/Sample/InterferenceItems.cpp
@@ -181,14 +181,6 @@ Interference2DParaCrystalItem::Interference2DParaCrystalItem()
         ->setToolTip("Probability distribution in first lattice direction");
     addGroupProperty(P_PDF2, info)
         ->setToolTip("Probability distribution in second lattice direction");
-
-    mapper()->setOnChildPropertyChange([this](SessionItem* item, const QString& property) {
-        if (item->hasModelType<GroupItem>() && GroupItem::isItemsTagName(property) && isTag(P_PDF1)
-            && isTag(P_PDF2))
-            update_distribution_displaynames();
-    });
-
-    update_distribution_displaynames();
 }
 
 std::unique_ptr<IInterference> Interference2DParaCrystalItem::createInterference() const
@@ -260,28 +252,6 @@ Interference2DParaCrystalItem::probabilityDistribution2() const
     return SelectionDescriptor<FTDistribution2DItem*>(item<GroupItem>(P_PDF2));
 }
 
-void Interference2DParaCrystalItem::update_distribution_displaynames()
-{
-    auto* group1 = dynamic_cast<GroupItem*>(getItem(P_PDF1));
-    auto* group2 = dynamic_cast<GroupItem*>(getItem(P_PDF2));
-
-    if (!group1 || !group2)
-        return;
-
-    SessionItem* pdf1 = group1->currentItem();
-    SessionItem* pdf2 = group2->currentItem();
-
-    if (pdf1 && pdf2) {
-        if (pdf1->modelType() == pdf2->modelType()) {
-            pdf1->setDisplayName(pdf1->modelType() + QString::number(0));
-            pdf2->setDisplayName(pdf2->modelType() + QString::number(1));
-        } else {
-            pdf1->setDisplayName(pdf1->modelType());
-            pdf2->setDisplayName(pdf2->modelType());
-        }
-    }
-}
-
 // --------------------------------------------------------------------------------------------- //
 
 InterferenceFinite2DLatticeItem::InterferenceFinite2DLatticeItem()
diff --git a/GUI/Model/Sample/InterferenceItems.h b/GUI/Model/Sample/InterferenceItems.h
index 18ee21c7b05..d62a7721718 100644
--- a/GUI/Model/Sample/InterferenceItems.h
+++ b/GUI/Model/Sample/InterferenceItems.h
@@ -117,9 +117,6 @@ public:
 
     SelectionDescriptor<FTDistribution2DItem*> probabilityDistribution2() const;
     template <typename T> T* setPDF2Type();
-
-private:
-    void update_distribution_displaynames();
 };
 
 class InterferenceFinite2DLatticeItem : public Interference2DAbstractLatticeItem {
-- 
GitLab


From e4816af79546cc09793d9f7a687ec95a1d0537ab Mon Sep 17 00:00:00 2001
From: Matthias Puchner <github@mpuchner.de>
Date: Thu, 9 Dec 2021 08:30:32 +0100
Subject: [PATCH 10/15] docu

---
 GUI/Model/Sample/ParticleLayoutItem.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/GUI/Model/Sample/ParticleLayoutItem.h b/GUI/Model/Sample/ParticleLayoutItem.h
index 95a1b26e47d..47c06bb4b7d 100644
--- a/GUI/Model/Sample/ParticleLayoutItem.h
+++ b/GUI/Model/Sample/ParticleLayoutItem.h
@@ -58,7 +58,7 @@ public:
     void setInterference(InterferenceItem* interference);
     void removeInterference();
 
-    // #baMigration Use only while not migrated to SessionModel!
+    // #baMigration Use only while not migrated from SessionModel!
     void enableDensity(bool b);
 
     //! Returns whether total density is defined by the currently selected interference.
-- 
GitLab


From 88f4b0b6351d6ddf50b281657569894de1e9d2dc Mon Sep 17 00:00:00 2001
From: Matthias Puchner <github@mpuchner.de>
Date: Thu, 9 Dec 2021 08:44:09 +0100
Subject: [PATCH 11/15] free ItemWithParticles from SessionItem signaling

---
 GUI/Model/Job/ParameterTreeUtils.cpp     |  3 +
 GUI/Model/Sample/ItemWithParticles.cpp   | 32 ++++------
 GUI/Model/Sample/ItemWithParticles.h     |  4 ++
 Tests/Unit/GUI/TestParticleCoreShell.cpp | 80 ------------------------
 Tests/Unit/GUI/TestParticleItem.cpp      | 20 ------
 5 files changed, 19 insertions(+), 120 deletions(-)
 delete mode 100644 Tests/Unit/GUI/TestParticleCoreShell.cpp

diff --git a/GUI/Model/Job/ParameterTreeUtils.cpp b/GUI/Model/Job/ParameterTreeUtils.cpp
index d5f095b9948..3cc4eba2a72 100644
--- a/GUI/Model/Job/ParameterTreeUtils.cpp
+++ b/GUI/Model/Job/ParameterTreeUtils.cpp
@@ -60,6 +60,9 @@ void handleItem(ParameterContainerItem* container, SessionItem* tree, SessionIte
 
     for (SessionItem* child : source->children()) {
 
+        if (auto* c = dynamic_cast<ItemWithParticles*>(child))
+            c->enableAbundance(!c->parentHasOwnAbundance());
+
         // later in handleItems the position of a shell particle has to be ignored.
         if (auto* c = dynamic_cast<ParticleCoreShellItem*>(child))
             if (c->shell())
diff --git a/GUI/Model/Sample/ItemWithParticles.cpp b/GUI/Model/Sample/ItemWithParticles.cpp
index e4cb2ac8133..7d83a527051 100644
--- a/GUI/Model/Sample/ItemWithParticles.cpp
+++ b/GUI/Model/Sample/ItemWithParticles.cpp
@@ -25,18 +25,6 @@
 #include "Sample/Particle/IParticle.h"
 #include "Sample/Scattering/Rotations.h"
 
-namespace {
-
-bool hasOwnAbundance(const SessionItem* item)
-{
-    static QStringList special_parent{ParticleCoreShellItem::M_TYPE,
-                                      ParticleCompositionItem::M_TYPE, MesoCrystalItem::M_TYPE};
-
-    return item ? special_parent.contains(item->modelType()) : false;
-}
-
-} // namespace
-
 DoubleDescriptor ItemWithParticles::abundance() const
 {
     return DoubleDescriptor(getItem(P_ABUNDANCE), Unit::unitless);
@@ -52,6 +40,18 @@ SessionItem* ItemWithParticles::abundanceItem() const
     return getItem(P_ABUNDANCE);
 }
 
+void ItemWithParticles::enableAbundance(bool b)
+{
+    getItem(P_ABUNDANCE)->setEnabled(b);
+}
+
+bool ItemWithParticles::parentHasOwnAbundance() const
+{
+    return dynamic_cast<ParticleCoreShellItem*>(parent())
+           || dynamic_cast<ParticleCompositionItem*>(parent())
+           || dynamic_cast<MesoCrystalItem*>(parent());
+}
+
 R3 ItemWithParticles::position() const
 {
     return item<VectorItem>(P_POSITION)->getVector();
@@ -151,14 +151,6 @@ ItemWithParticles::ItemWithParticles(const QString& model_type, const QString& a
     addProperty<VectorItem>(P_POSITION)->setToolTip(position_tooltip);
 
     registerTag(T_TRANSFORMATION, 0, 1, {TransformationItem::M_TYPE});
-
-    mapper()->setOnParentChange([this](SessionItem* parent) {
-        if (hasOwnAbundance(parent)) {
-            setItemValue(P_ABUNDANCE, 1.0);
-            getItem(P_ABUNDANCE)->setEnabled(false);
-        } else
-            getItem(P_ABUNDANCE)->setEnabled(true);
-    });
 }
 
 void ItemWithParticles::setDefaultTagTransformation()
diff --git a/GUI/Model/Sample/ItemWithParticles.h b/GUI/Model/Sample/ItemWithParticles.h
index 5e243ac849e..a965189ffd8 100644
--- a/GUI/Model/Sample/ItemWithParticles.h
+++ b/GUI/Model/Sample/ItemWithParticles.h
@@ -38,6 +38,10 @@ public:
     DoubleDescriptor abundance() const;
     void setAbundance(double abundance);
     SessionItem* abundanceItem() const;
+    bool parentHasOwnAbundance() const;
+
+    // #baMigration Use only while not migrated from SessionModel!
+    void enableAbundance(bool b);
 
     R3 position() const;
     void setPosition(const R3& position);
diff --git a/Tests/Unit/GUI/TestParticleCoreShell.cpp b/Tests/Unit/GUI/TestParticleCoreShell.cpp
deleted file mode 100644
index 470a316ca84..00000000000
--- a/Tests/Unit/GUI/TestParticleCoreShell.cpp
+++ /dev/null
@@ -1,80 +0,0 @@
-#include "GUI/Model/Material/MaterialModel.h"
-#include "GUI/Model/Sample/ParticleCompositionItem.h"
-#include "GUI/Model/Sample/ParticleCoreShellItem.h"
-#include "GUI/Model/Sample/ParticleItem.h"
-#include "GUI/Model/Sample/SampleModel.h"
-#include "GUI/Model/Session/SessionItemUtils.h"
-#include "GUI/Model/Types/DoubleDescriptor.h"
-#include "GUI/Model/Types/VectorItem.h"
-#include "Tests/GTestWrapper/google_test.h"
-#include <QtTest>
-
-using namespace GUI::Session::ItemUtils;
-
-class TestParticleCoreShell : public ::testing::Test {
-};
-
-//! Checking that adding and removing core/shell leads to enabling/disabling of their position
-//! and abundance properties.
-
-TEST_F(TestParticleCoreShell, propertyAppearance)
-{
-    SampleModel model;
-    MaterialModel materials;
-    materials.addRefractiveMaterial("Default", 1e-3, 1e-5);
-
-    // empty coreshell particle
-    auto* coreshell = model.insertItem<ParticleCoreShellItem>();
-    EXPECT_TRUE(coreshell->abundanceItem()->isEnabled());
-    EXPECT_EQ(coreshell->abundance(), 1.0);
-    EXPECT_TRUE(coreshell->positionItem()->isEnabled());
-    R3 pos = coreshell->positionItem()->getVector();
-    EXPECT_EQ(pos.x(), 0.0);
-    EXPECT_EQ(pos.y(), 0.0);
-    EXPECT_EQ(pos.z(), 0.0);
-
-    // adding core, and checking that abundance is disabled
-    auto* core = coreshell->createCore(&materials);
-    EXPECT_FALSE(core->abundanceItem()->isEnabled());
-    EXPECT_TRUE(core->positionItem()->isEnabled());
-
-    // removing core, checking that abundance restored
-    coreshell->takeRow(ParentRow(*core));
-    EXPECT_TRUE(core->abundanceItem()->isEnabled());
-    EXPECT_TRUE(core->positionItem()->isEnabled());
-    delete core;
-
-    // create a shell and checking abundance disabled
-    coreshell->createShell(&materials);
-    EXPECT_FALSE(coreshell->shell()->abundanceItem()->isEnabled());
-    EXPECT_FALSE(coreshell->shell()->positionItem()->isEnabled());
-    // checking that position and abundance values were reset to defaults
-    EXPECT_EQ(coreshell->shell()->positionItem()->x(), 0.0);
-    EXPECT_EQ(coreshell->shell()->abundance(), 1.0);
-}
-
-
-//! Checking that abundance gets disabled in particle composition context.
-
-TEST_F(TestParticleCoreShell, compositionContext)
-{
-    SampleModel model;
-
-    // coreshell particle
-    auto* coreshell = model.insertItem<ParticleCoreShellItem>();
-    coreshell->setAbundance(0.2);
-    EXPECT_TRUE(coreshell->abundanceItem()->isEnabled());
-    EXPECT_EQ(coreshell->abundance(), 0.2);
-
-    // create composition, adding coreshell to it
-    auto* composition = model.insertItem<ParticleCompositionItem>();
-    composition->addParticle(coreshell);
-    // checking abundance has switched to defaults
-    EXPECT_FALSE(coreshell->abundanceItem()->isEnabled());
-    EXPECT_EQ(coreshell->abundance(), 1.0);
-
-    // removing coreshell
-    composition->takeRow(ParentRow(*coreshell));
-    EXPECT_TRUE(coreshell->abundanceItem()->isEnabled());
-    delete coreshell;
-}
diff --git a/Tests/Unit/GUI/TestParticleItem.cpp b/Tests/Unit/GUI/TestParticleItem.cpp
index 7747e629803..04145e20def 100644
--- a/Tests/Unit/GUI/TestParticleItem.cpp
+++ b/Tests/Unit/GUI/TestParticleItem.cpp
@@ -25,23 +25,3 @@ TEST_F(TestParticleItem, InitialState)
     EXPECT_EQ(group->displayName(), "Form Factor");
     EXPECT_EQ(group->children().size(), 1);
 }
-
-TEST_F(TestParticleItem, compositionContext)
-{
-    SampleModel model;
-    auto* particle = model.insertItem<ParticleItem>();
-    particle->setAbundance(0.2);
-    EXPECT_TRUE(particle->abundanceItem()->isEnabled());
-    EXPECT_EQ(particle->abundance(), 0.2);
-
-    // adding particle to composition, checking that abundance is default
-    auto* composition = model.insertItem<ParticleCompositionItem>();
-    composition->addParticle(particle);
-    EXPECT_FALSE(particle->abundanceItem()->isEnabled());
-    EXPECT_EQ(particle->abundance(), 1.0);
-
-    // removing particle, checking that abundance is enabled again
-    composition->takeRow(ParentRow(*particle));
-    EXPECT_TRUE(particle->abundanceItem()->isEnabled());
-    delete particle;
-}
-- 
GitLab


From 82cefa40ded0d8dd347725dc1c54180b03e205c1 Mon Sep 17 00:00:00 2001
From: Matthias Puchner <github@mpuchner.de>
Date: Thu, 9 Dec 2021 08:46:17 +0100
Subject: [PATCH 12/15] fix tuning tree hierarchy

---
 GUI/Model/Job/ParameterTreeUtils.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/GUI/Model/Job/ParameterTreeUtils.cpp b/GUI/Model/Job/ParameterTreeUtils.cpp
index 3cc4eba2a72..5b148256c3d 100644
--- a/GUI/Model/Job/ParameterTreeUtils.cpp
+++ b/GUI/Model/Job/ParameterTreeUtils.cpp
@@ -116,7 +116,7 @@ void GUI::Model::ParameterTreeUtils::createParameterTree(JobItem* jobItem,
     auto* materialTopLabel = container->model()->insertItem<ParameterLabelItem>(container);
     materialTopLabel->setDisplayName("Materials");
     for (auto* material : jobItem->materialItems().materialItems()) {
-        auto* materialLabel = container->model()->insertItem<ParameterLabelItem>(container);
+        auto* materialLabel = container->model()->insertItem<ParameterLabelItem>(materialTopLabel);
         materialLabel->setDisplayName(material->materialName());
 
         DoubleDescriptors descriptors;
-- 
GitLab


From 4ee3eb6a2012530f132464f6e1024348c7dbf04a Mon Sep 17 00:00:00 2001
From: Matthias Puchner <github@mpuchner.de>
Date: Thu, 9 Dec 2021 11:21:35 +0100
Subject: [PATCH 13/15] rm unpractical const

---
 GUI/Model/Project/OutputDataIOService.cpp | 18 +++++-------------
 GUI/Model/Session/ModelPath.cpp           |  8 ++------
 GUI/Model/Session/ModelPath.h             |  5 ++++-
 3 files changed, 11 insertions(+), 20 deletions(-)

diff --git a/GUI/Model/Project/OutputDataIOService.cpp b/GUI/Model/Project/OutputDataIOService.cpp
index 4d8e84edecb..f7649109643 100644
--- a/GUI/Model/Project/OutputDataIOService.cpp
+++ b/GUI/Model/Project/OutputDataIOService.cpp
@@ -24,7 +24,11 @@
 
 namespace {
 
-JobItem* parentJobItem(SaveLoadInterface* item);
+JobItem* parentJobItem(SaveLoadInterface* item)
+{
+    auto* session_item = dynamic_cast<SessionItem*>(item); // sidecast
+    return dynamic_cast<JobItem*>(GUI::Model::Path::ancestor(session_item, JobItem::M_TYPE));
+}
 
 } // namespace
 
@@ -124,15 +128,3 @@ void OutputDataIOService::cleanOldFiles(const QString& projectDir, const QString
     for (const auto& name : toRemove)
         QFile::remove(projectDir + "/" + name);
 }
-
-namespace {
-
-JobItem* parentJobItem(SaveLoadInterface* item)
-{
-    auto* session_item = dynamic_cast<SessionItem*>(item); // sidecast
-    const auto* jobItem =
-        dynamic_cast<const JobItem*>(GUI::Model::Path::ancestor(session_item, JobItem::M_TYPE));
-    return const_cast<JobItem*>(jobItem);
-}
-
-} // namespace
diff --git a/GUI/Model/Session/ModelPath.cpp b/GUI/Model/Session/ModelPath.cpp
index 5f74fd2790d..75c05a8ca63 100644
--- a/GUI/Model/Session/ModelPath.cpp
+++ b/GUI/Model/Session/ModelPath.cpp
@@ -80,13 +80,9 @@ bool GUI::Model::Path::isValidItem(SessionModel* model, SessionItem* item,
     return false;
 }
 
-//! Returns ancestor of given modelType for given item.
-//! For example, returns corresponding jobItem owning ParameterItem via ParameterContainer.
-
-const SessionItem* GUI::Model::Path::ancestor(const SessionItem* item,
-                                              const QString& requiredModelType)
+SessionItem* GUI::Model::Path::ancestor(SessionItem* item, const QString& requiredModelType)
 {
-    const SessionItem* cur = item;
+    SessionItem* cur = item;
     while (cur && cur->modelType() != requiredModelType)
         cur = cur->parent();
 
diff --git a/GUI/Model/Session/ModelPath.h b/GUI/Model/Session/ModelPath.h
index 6959793cd73..94cc394a0cc 100644
--- a/GUI/Model/Session/ModelPath.h
+++ b/GUI/Model/Session/ModelPath.h
@@ -38,7 +38,10 @@ SessionItem* getItemFromPath(const QString& relPath, const SessionItem* parent);
 
 bool isValidItem(SessionModel* model, SessionItem* item, const QModelIndex& parent);
 
-const SessionItem* ancestor(const SessionItem* item, const QString& requiredModelType);
+//! Returns ancestor of given modelType for given item.
+//!
+//! For example, returns corresponding jobItem owning ParameterItem via ParameterContainer.
+SessionItem* ancestor(SessionItem* item, const QString& requiredModelType);
 
 } // namespace GUI::Model::Path
 
-- 
GitLab


From 7de5461b69c1decb1f30ba838354322eb1aa64a5 Mon Sep 17 00:00:00 2001
From: Matthias Puchner <github@mpuchner.de>
Date: Thu, 9 Dec 2021 14:47:52 +0100
Subject: [PATCH 14/15] add signals to sample model and use them instead of
 generic SessionModel signals

---
 GUI/Model/Data/ApplicationModels.cpp    | 1 -
 GUI/Model/Project/ProjectDocument.cpp   | 5 +++++
 GUI/Model/Sample/SampleModel.cpp        | 8 ++++++++
 GUI/Model/Sample/SampleModel.h          | 4 ++++
 GUI/View/SampleDesigner/ScriptPanel.cpp | 9 +--------
 5 files changed, 18 insertions(+), 9 deletions(-)

diff --git a/GUI/Model/Data/ApplicationModels.cpp b/GUI/Model/Data/ApplicationModels.cpp
index b6b1a083daa..3888f2d2224 100644
--- a/GUI/Model/Data/ApplicationModels.cpp
+++ b/GUI/Model/Data/ApplicationModels.cpp
@@ -34,7 +34,6 @@ ApplicationModels::ApplicationModels(QObject* parent)
     m_realDataModel = new RealDataModel(this);
     m_jobModel = new JobModel(this);
 
-    connectModel(m_sampleModel);
     connectModel(m_instrumentModel);
     connectModel(m_realDataModel);
     connectModel(m_jobModel);
diff --git a/GUI/Model/Project/ProjectDocument.cpp b/GUI/Model/Project/ProjectDocument.cpp
index b97371e418e..4b3a5a83c09 100644
--- a/GUI/Model/Project/ProjectDocument.cpp
+++ b/GUI/Model/Project/ProjectDocument.cpp
@@ -63,6 +63,11 @@ ProjectDocument::ProjectDocument(const QString& projectFileName)
     connect(&m_materials, &MaterialModel::materialChanged, this, &ProjectDocument::onModelChanged,
             Qt::UniqueConnection);
 
+    connect(sampleModel(), &SampleModel::sampleAddedOrRemoved, this,
+            &ProjectDocument::onModelChanged, Qt::UniqueConnection);
+    connect(sampleModel(), &SampleModel::sampleChanged, this, &ProjectDocument::onModelChanged,
+            Qt::UniqueConnection);
+
     m_linkManager = std::make_unique<LinkInstrumentManager>(instrumentModel(), realDataModel());
     setObjectName("ProjectDocument");
     if (!projectFileName.isEmpty())
diff --git a/GUI/Model/Sample/SampleModel.cpp b/GUI/Model/Sample/SampleModel.cpp
index c5d28d0cd74..e8db5919479 100644
--- a/GUI/Model/Sample/SampleModel.cpp
+++ b/GUI/Model/Sample/SampleModel.cpp
@@ -16,6 +16,7 @@
 #include "GUI/Model/Group/PropertyItem.h"
 #include "GUI/Model/Sample/ItemWithMaterial.h"
 #include "GUI/Model/Sample/MultiLayerItem.h"
+#include "GUI/Model/Session/ModelPath.h"
 #include "GUI/Model/Session/ModelUtils.h"
 #include <QApplication>
 #include <QFontMetrics>
@@ -80,11 +81,13 @@ QVector<ItemWithMaterial*> SampleModel::itemsWithMaterial() const
 MultiLayerItem* SampleModel::addMultiLayer()
 {
     return insertItem<MultiLayerItem>();
+    emit sampleAddedOrRemoved();
 }
 
 void SampleModel::removeMultiLayer(MultiLayerItem* item)
 {
     removeItem(item);
+    emit sampleAddedOrRemoved();
 }
 
 void SampleModel::onDataChanged(const QModelIndex& index, const QModelIndex&)
@@ -93,4 +96,9 @@ void SampleModel::onDataChanged(const QModelIndex& index, const QModelIndex&)
     auto* const item = itemForIndex(index);
     if (item)
         emit itemDataChanged(item);
+
+    auto* multiLayerItem =
+        dynamic_cast<MultiLayerItem*>(GUI::Model::Path::ancestor(item, MultiLayerItem::M_TYPE));
+    if (multiLayerItem)
+        emit sampleChanged(multiLayerItem);
 }
diff --git a/GUI/Model/Sample/SampleModel.h b/GUI/Model/Sample/SampleModel.h
index 591441a369e..bc6426cd49c 100644
--- a/GUI/Model/Sample/SampleModel.h
+++ b/GUI/Model/Sample/SampleModel.h
@@ -49,6 +49,10 @@ signals:
     //! item is the one given in the first index of dataChanged(idx1,idx2)
     void itemDataChanged(const SessionItem* item);
 
+signals:
+    void sampleAddedOrRemoved();
+    void sampleChanged(MultiLayerItem* multiLayerItem);
+
 private:
     void onDataChanged(const QModelIndex& index, const QModelIndex&);
 };
diff --git a/GUI/View/SampleDesigner/ScriptPanel.cpp b/GUI/View/SampleDesigner/ScriptPanel.cpp
index 6c74ff967d3..cf03707013a 100644
--- a/GUI/View/SampleDesigner/ScriptPanel.cpp
+++ b/GUI/View/SampleDesigner/ScriptPanel.cpp
@@ -84,20 +84,13 @@ void ScriptPanel::updateEditor()
 void ScriptPanel::setEditorConnected(bool isConnected)
 {
     if (isConnected) {
-        connect(m_sampleModel, &SampleModel::rowsInserted, m_updateTimer,
+        connect(m_sampleModel, &SampleModel::sampleChanged, m_updateTimer,
                 &UpdateTimer::scheduleUpdate);
-        connect(m_sampleModel, &SampleModel::rowsRemoved, m_updateTimer,
-                &UpdateTimer::scheduleUpdate);
-        connect(m_sampleModel, &SampleModel::itemDataChanged, m_updateTimer,
-                &UpdateTimer::scheduleUpdate);
-        connect(m_sampleModel, &SampleModel::modelReset, this, &ScriptPanel::updateEditor,
-                Qt::UniqueConnection);
         connect(m_updateTimer, &UpdateTimer::timeToUpdate, this, &ScriptPanel::updateEditor,
                 Qt::UniqueConnection);
 
         m_updateTimer->scheduleUpdate();
     } else {
-        m_sampleModel->disconnect(this);
         m_sampleModel->disconnect(m_updateTimer);
         m_updateTimer->disconnect(this);
     }
-- 
GitLab


From f61af160d76c65e985fe65d80e2c93fb50caa073 Mon Sep 17 00:00:00 2001
From: Matthias Puchner <github@mpuchner.de>
Date: Thu, 9 Dec 2021 15:54:23 +0100
Subject: [PATCH 15/15] fix GUI->domain transformation

---
 GUI/Model/To/ToDomain.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/GUI/Model/To/ToDomain.cpp b/GUI/Model/To/ToDomain.cpp
index 3e2ff9eed3e..133d2f506dc 100644
--- a/GUI/Model/To/ToDomain.cpp
+++ b/GUI/Model/To/ToDomain.cpp
@@ -147,7 +147,7 @@ GUI::Transform::ToDomain::buildMultiLayer(const MultiLayerItem& multiLayerItem)
         if (P_layer) {
             const auto roughness = layerItem->roughness().currentItem();
             auto P_roughness = createLayerRoughness(roughness);
-            if (P_roughness && !isLastLayer)
+            if (P_roughness && !isFirstLayer)
                 P_multilayer->addLayerWithTopRoughness(*P_layer, *P_roughness);
             else
                 P_multilayer->addLayer(*P_layer);
-- 
GitLab