diff --git a/bioexplorer/backend/science/BioExplorerPlugin.cpp b/bioexplorer/backend/science/BioExplorerPlugin.cpp index 183707590..cb5137ab7 100644 --- a/bioexplorer/backend/science/BioExplorerPlugin.cpp +++ b/bioexplorer/backend/science/BioExplorerPlugin.cpp @@ -385,6 +385,12 @@ void BioExplorerPlugin::init() [&](const ModelIdDetails &payload) -> IdsDetails { return _getModelInstances(payload); }); + endPoint = PLUGIN_API_PREFIX + "add-model-instance"; + PLUGIN_REGISTER_ENDPOINT(endPoint); + actionInterface->registerRequest(endPoint, + [&](const AddModelInstanceDetails &payload) + { return _addModelInstance(payload); }); + endPoint = PLUGIN_API_PREFIX + "get-model-name"; PLUGIN_REGISTER_ENDPOINT(endPoint); actionInterface->registerRequest(endPoint, @@ -1471,6 +1477,32 @@ IdsDetails BioExplorerPlugin::_getModelInstances(const ModelIdDetails &payload) return instanceIds; } +Response BioExplorerPlugin::_addModelInstance(const AddModelInstanceDetails &payload) const +{ + Response response; + try + { + auto &scene = _api->getScene(); + auto modelDescriptor = scene.getModel(payload.modelId); + if (!modelDescriptor) + PLUGIN_THROW("Invalid model Id"); + + Transformation tf; + const auto translation = doublesToVector3d(payload.translation); + const auto rotation = doublesToQuaterniond(payload.rotation); + const auto rotationCenter = doublesToVector3d(payload.rotationCenter); + const auto scale = doublesToVector3d(payload.scale); + tf.setTranslation(translation); + tf.setRotation(rotation); + tf.setRotationCenter(rotationCenter); + tf.setScale(scale); + const ModelInstance instance(true, false, tf); + modelDescriptor->addInstance(instance); + } + CATCH_STD_EXCEPTION() + return response; +} + NameDetails BioExplorerPlugin::_getModelName(const ModelIdDetails &payload) const { NameDetails modelName; diff --git a/bioexplorer/backend/science/BioExplorerPlugin.h b/bioexplorer/backend/science/BioExplorerPlugin.h index 2d17d7516..b8f6ea8b9 100644 --- a/bioexplorer/backend/science/BioExplorerPlugin.h +++ b/bioexplorer/backend/science/BioExplorerPlugin.h @@ -112,6 +112,7 @@ class BioExplorerPlugin : public core::ExtensionPlugin details::ModelBoundsDetails _getModelBounds(const details::ModelIdDetails &payload) const; details::IdsDetails _getModelIds() const; details::IdsDetails _getModelInstances(const details::ModelIdDetails &payload) const; + details::Response _addModelInstance(const details::AddModelInstanceDetails &payload) const; // Colors and materials details::Response _setProteinColorScheme(const details::ProteinColorSchemeDetails &payload) const; diff --git a/bioexplorer/backend/science/api/Params.cpp b/bioexplorer/backend/science/api/Params.cpp index 8d3cb1e64..d5a0d0b61 100644 --- a/bioexplorer/backend/science/api/Params.cpp +++ b/bioexplorer/backend/science/api/Params.cpp @@ -560,6 +560,24 @@ bool from_json(ModelIdDetails ¶m, const std::string &payload) return true; } +bool from_json(AddModelInstanceDetails ¶m, const std::string &payload) +{ + try + { + auto js = nlohmann::json::parse(payload); + FROM_JSON(param, js, modelId); + FROM_JSON(param, js, translation); + FROM_JSON(param, js, rotation); + FROM_JSON(param, js, rotationCenter); + FROM_JSON(param, js, scale); + } + catch (...) + { + return false; + } + return true; +} + std::string to_json(const ModelBoundsDetails ¶m) { try diff --git a/bioexplorer/backend/science/api/Params.h b/bioexplorer/backend/science/api/Params.h index 28276f349..d9993a7f0 100644 --- a/bioexplorer/backend/science/api/Params.h +++ b/bioexplorer/backend/science/api/Params.h @@ -80,6 +80,7 @@ bool from_json(bioexplorer::details::DatabaseAccessDetails ¶m, const std::st // Models, Color schemes and materials bool from_json(bioexplorer::details::ProteinColorSchemeDetails ¶m, const std::string &payload); bool from_json(bioexplorer::details::ModelIdDetails &modelId, const std::string &payload); +bool from_json(bioexplorer::details::AddModelInstanceDetails ¶m, const std::string &payload); std::string to_json(const bioexplorer::details::ModelBoundsDetails ¶m); bool from_json(bioexplorer::details::MaterialsDetails &materialsDetails, const std::string &payload); std::string to_json(const bioexplorer::details::IdsDetails ¶m); diff --git a/bioexplorer/backend/science/common/Types.h b/bioexplorer/backend/science/common/Types.h index 4bc71af85..b3210bc9a 100644 --- a/bioexplorer/backend/science/common/Types.h +++ b/bioexplorer/backend/science/common/Types.h @@ -1090,6 +1090,24 @@ typedef struct size_t maxNbInstances; } ModelIdDetails; +/** + * @brief Add instance to model + * + */ +typedef struct +{ + /** Model identifier */ + size_t modelId; + /** Translation */ + doubles translation; + /** Translation */ + doubles rotation; + /** Translation */ + doubles rotationCenter; + /** Translation */ + doubles scale; +} AddModelInstanceDetails; + /** * @brief Model identifier * diff --git a/bioexplorer/pythonsdk/bioexplorer/bio_explorer.py b/bioexplorer/pythonsdk/bioexplorer/bio_explorer.py index 9514acb65..67307a4c4 100644 --- a/bioexplorer/pythonsdk/bioexplorer/bio_explorer.py +++ b/bioexplorer/pythonsdk/bioexplorer/bio_explorer.py @@ -2257,6 +2257,36 @@ def get_model_instances(self, model_id, max_number_of_instances=1e6): params["maxNbInstances"] = max_number_of_instances return self._invoke("get-model-instances", params) + def add_model_instance( + self, + model_id, + translation=Vector3(), + rotation=Quaternion(), + rotation_center=Vector3(), + scale=Vector3(1.0, 1.0, 1.0), + ): + """ + Return the list of instances in the specified model + + :model_id: Id of the model + :max_number_of_instances: Maximum number of instances (this can be huge and difficult to + handle by the python code) + :return: List of instance Ids + """ + assert isinstance(model_id, int) + assert isinstance(translation, Vector3) + assert isinstance(rotation, Quaternion) + assert isinstance(rotation_center, Vector3) + assert isinstance(scale, Vector3) + + params = dict() + params["modelId"] = model_id + params["translation"] = translation.to_list() + params["rotation"] = list(rotation) + params["rotationCenter"] = rotation_center.to_list() + params["scale"] = scale.to_list() + return self._invoke("add-model-instance", params) + def get_material_ids(self, model_id): """ Return the list of material Ids for a given model