Merge pull request #20549 from davesrocketshop/external_manager_pr

Material: Interface with an external module
This commit is contained in:
Chris Hennes
2025-04-14 11:23:53 -05:00
committed by GitHub
8 changed files with 1203 additions and 7 deletions

View File

@@ -6,6 +6,10 @@ endif(MSVC)
add_definitions(-DYAML_CPP_STATIC_DEFINE)
if(BUILD_MATERIAL_EXTERNAL)
add_definitions(-DBUILD_MATERIAL_EXTERNAL)
endif(BUILD_MATERIAL_EXTERNAL)
include_directories(
${CMAKE_BINARY_DIR}
${CMAKE_BINARY_DIR}/src
@@ -55,6 +59,11 @@ generate_from_py(MaterialProperty)
generate_from_py(Model)
generate_from_py(UUIDs)
SET(MaterialsAPI_Files
MaterialAPI/__init__.py
MaterialAPI/MaterialManagerExternal.py
)
SET(Python_SRCS
Exceptions.h
Array2D.pyi
@@ -126,6 +135,12 @@ SET(Materials_SRCS
PyVariants.h
trim.h
)
if(BUILD_MATERIAL_EXTERNAL)
list(APPEND Materials_SRCS
ExternalManager.cpp
ExternalManager.h
)
endif(BUILD_MATERIAL_EXTERNAL)
if(FREECAD_USE_PCH)
add_definitions(-D_PreComp_)
@@ -143,3 +158,14 @@ SET_BIN_DIR(Materials Materials /Mod/Material)
SET_PYTHON_PREFIX_SUFFIX(Materials)
INSTALL(TARGETS Materials DESTINATION ${CMAKE_INSTALL_LIBDIR})
ADD_CUSTOM_TARGET(MaterialsAPILib ALL
SOURCES ${MaterialsAPI_Files} ${Material_QRC_SRCS}
)
fc_target_copy_resource(MaterialsAPILib
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_BINARY_DIR}/Mod/Material
${MaterialsAPI_Files})
INSTALL(FILES ${MaterialsAPI_Files} DESTINATION Mod/Material/MaterialAPI)

View File

@@ -0,0 +1,786 @@
/***************************************************************************
* Copyright (c) 2024 David Carter <dcarter@david.carter.ca> *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
**************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
#endif
#include <Python.h>
#include <QMutex>
#include <QMutexLocker>
#include <App/Application.h>
#include <Base/Console.h>
#include <Base/Interpreter.h>
#include <CXX/Objects.hxx>
#include "Exceptions.h"
#include "ExternalManager.h"
#include "MaterialLibrary.h"
#include "MaterialLibraryPy.h"
#include "MaterialPy.h"
#include "ModelLibrary.h"
#include "ModelPy.h"
#include "MaterialFilterPy.h"
#include "MaterialFilterOptionsPy.h"
using namespace Materials;
/* TRANSLATOR Material::Materials */
ExternalManager* ExternalManager::_manager = nullptr;
QMutex ExternalManager::_mutex;
ExternalManager::ExternalManager()
: _instantiated(false)
{
_hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Mod/Material/ExternalInterface");
_hGrp->Attach(this);
getConfiguration();
}
ExternalManager::~ExternalManager()
{
_hGrp->Detach(this);
}
void ExternalManager::OnChange(ParameterGrp::SubjectType& /*rCaller*/, ParameterGrp::MessageType Reason)
{
if (std::strncmp(Reason, "Current", 7) == 0) {
if (_instantiated) {
// The old manager object will be deleted when reconnecting
_instantiated = false;
}
getConfiguration();
}
}
void ExternalManager::getConfiguration()
{
// _hGrp = App::GetApplication().GetParameterGroupByPath(
// "User parameter:BaseApp/Preferences/Mod/Material/ExternalInterface");
auto current = _hGrp->GetASCII("Current", "None");
if (current == "None") {
_moduleName = "";
_className = "";
}
else {
auto groupName =
"User parameter:BaseApp/Preferences/Mod/Material/ExternalInterface/Interfaces/"
+ current;
auto hGrp = App::GetApplication().GetParameterGroupByPath(groupName.c_str());
_moduleName = hGrp->GetASCII("Module", "");
_className = hGrp->GetASCII("Class", "");
}
}
void ExternalManager::instantiate()
{
_instantiated = false;
Base::Console().Log("Loading external manager...\n");
if (_moduleName.empty() || _className.empty()) {
Base::Console().Log("External module not defined\n");
return;
}
try {
Base::PyGILStateLocker lock;
Py::Module mod(PyImport_ImportModule(_moduleName.c_str()), true);
if (mod.isNull()) {
Base::Console().Log(" failed\n");
return;
}
Py::Callable managerClass(mod.getAttr(_className));
_managerObject = managerClass.apply();
if (_managerObject.hasAttr("APIVersion")) {
_instantiated = true;
}
if (_instantiated) {
Base::Console().Log("done\n");
}
else {
Base::Console().Log("failed\n");
}
}
catch (Py::Exception& e) {
Base::Console().Log("failed\n");
e.clear();
}
}
void ExternalManager::connect()
{
if (!_instantiated) {
instantiate();
if (!_instantiated) {
throw ConnectionError();
}
}
}
void ExternalManager::initManager()
{
QMutexLocker locker(&_mutex);
if (!_manager) {
_manager = new ExternalManager();
}
}
ExternalManager* ExternalManager::getManager()
{
initManager();
return _manager;
}
//=====
//
// Library management
//
//=====
bool ExternalManager::checkMaterialLibraryType(const Py::Object& entry)
{
return entry.hasAttr("name") && entry.hasAttr("icon") && entry.hasAttr("readOnly")
&& entry.hasAttr("timestamp");
}
std::shared_ptr<Library>
ExternalManager::libraryFromObject(const Py::Object& entry)
{
if (!checkMaterialLibraryType(entry)) {
throw InvalidLibrary();
}
Py::String pyName(entry.getAttr("name"));
Py::Bytes pyIcon(entry.getAttr("icon"));
Py::Boolean pyReadOnly(entry.getAttr("readOnly"));
Py::String pyTimestamp(entry.getAttr("timestamp"));
QString libraryName;
if (!pyName.isNone()) {
libraryName = QString::fromStdString(pyName.as_string());
}
QString icon;
if (!pyIcon.isNone()) {
icon = QString::fromStdString(pyIcon.as_string());
}
bool readOnly = pyReadOnly.as_bool();
QString timestamp;
if (!pyTimestamp.isNone()) {
timestamp = QString::fromStdString(pyTimestamp.as_string());
}
auto library = std::make_shared<Library>(libraryName, icon, readOnly, timestamp);
return library;
}
bool ExternalManager::checkMaterialObjectType(const Py::Object& entry)
{
return entry.hasAttr("UUID") && entry.hasAttr("path") && entry.hasAttr("name");
}
std::tuple<QString, QString, QString>
ExternalManager::materialObjectTypeFromObject(const Py::Object& entry)
{
QString uuid;
auto pyUUID = entry.getAttr("UUID");
if (!pyUUID.isNone()) {
uuid = QString::fromStdString(pyUUID.as_string());
}
QString path;
auto pyPath = entry.getAttr("path");
if (!pyPath.isNone()) {
path = QString::fromStdString(pyPath.as_string());
}
QString name;
auto pyName = entry.getAttr("name");
if (!pyName.isNone()) {
name = QString::fromStdString(pyName.as_string());
}
return std::tuple<QString, QString, QString>(uuid, path, name);
}
std::shared_ptr<std::vector<std::shared_ptr<Library>>>
ExternalManager::libraries()
{
auto libList = std::make_shared<std::vector<std::shared_ptr<Library>>>();
connect();
Base::PyGILStateLocker lock;
try {
if (_managerObject.hasAttr("libraries")) {
Py::Callable libraries(_managerObject.getAttr("libraries"));
Py::List list(libraries.apply());
for (auto lib : list) {
auto library = libraryFromObject(Py::Object(lib));
libList->push_back(library);
}
}
else {
Base::Console().Log("\tlibraries() not found\n");
throw ConnectionError();
}
}
catch (Py::Exception& e) {
Base::PyException e1; // extract the Python error text
throw LibraryNotFound(e1.what());
}
return libList;
}
std::shared_ptr<std::vector<std::shared_ptr<Library>>> ExternalManager::modelLibraries()
{
auto libList = std::make_shared<std::vector<std::shared_ptr<Library>>>();
connect();
Base::PyGILStateLocker lock;
try {
if (_managerObject.hasAttr("modelLibraries")) {
Py::Callable libraries(_managerObject.getAttr("modelLibraries"));
Py::List list(libraries.apply());
for (auto lib : list) {
auto library = libraryFromObject(Py::Tuple(lib));
libList->push_back(library);
}
}
else {
Base::Console().Log("\tmodelLibraries() not found\n");
throw ConnectionError();
}
}
catch (Py::Exception& e) {
Base::PyException e1; // extract the Python error text
throw LibraryNotFound(e1.what());
}
return libList;
}
std::shared_ptr<std::vector<std::shared_ptr<Library>>> ExternalManager::materialLibraries()
{
auto libList = std::make_shared<std::vector<std::shared_ptr<Library>>>();
connect();
Base::PyGILStateLocker lock;
try {
if (_managerObject.hasAttr("materialLibraries")) {
Py::Callable libraries(_managerObject.getAttr("materialLibraries"));
Py::List list(libraries.apply());
for (auto lib : list) {
auto library = libraryFromObject(Py::Tuple(lib));
libList->push_back(library);
}
}
else {
Base::Console().Log("\tmaterialLibraries() not found\n");
throw ConnectionError();
}
}
catch (Py::Exception& e) {
Base::PyException e1; // extract the Python error text
throw LibraryNotFound(e1.what());
}
return libList;
}
std::shared_ptr<Library> ExternalManager::getLibrary(const QString& name)
{
// throw LibraryNotFound("Not yet implemented");
connect();
Base::PyGILStateLocker lock;
try {
if (_managerObject.hasAttr("getLibrary")) {
Py::Callable libraries(_managerObject.getAttr("getLibrary"));
Py::Tuple args(1);
args.setItem(0, Py::String(name.toStdString()));
Py::Tuple result(libraries.apply(args));
Py::Object libObject = result.getItem(0);
auto lib = libraryFromObject(Py::Tuple(libObject));
return std::make_shared<Library>(*lib);
}
else {
Base::Console().Log("\tgetLibrary() not found\n");
throw ConnectionError();
}
}
catch (Py::Exception& e) {
Base::PyException e1; // extract the Python error text
throw CreationError(e1.what());
}
}
void ExternalManager::createLibrary(const QString& libraryName, const QString& icon, bool readOnly)
{
connect();
Base::PyGILStateLocker lock;
try {
if (_managerObject.hasAttr("createLibrary")) {
Py::Callable libraries(_managerObject.getAttr("createLibrary"));
Py::Tuple args(3);
args.setItem(0, Py::String(libraryName.toStdString()));
args.setItem(1, Py::String(icon.toStdString()));
args.setItem(2, Py::Boolean(readOnly));
libraries.apply(args); // No return expected
}
else {
Base::Console().Log("\tcreateLibrary() not found\n");
throw ConnectionError();
}
}
catch (Py::Exception& e) {
Base::PyException e1; // extract the Python error text
throw CreationError(e1.what());
}
}
void ExternalManager::renameLibrary(const QString& libraryName, const QString& newName)
{
connect();
Base::PyGILStateLocker lock;
try {
if (_managerObject.hasAttr("renameLibrary")) {
Py::Callable libraries(_managerObject.getAttr("renameLibrary"));
Py::Tuple args(2);
args.setItem(0, Py::String(libraryName.toStdString()));
args.setItem(1, Py::String(newName.toStdString()));
libraries.apply(args); // No return expected
}
else {
Base::Console().Log("\trenameLibrary() not found\n");
throw ConnectionError();
}
}
catch (Py::Exception& e) {
Base::PyException e1; // extract the Python error text
throw RenameError(e1.what());
}
}
void ExternalManager::changeIcon(const QString& libraryName, const QString& icon)
{
connect();
Base::PyGILStateLocker lock;
try {
if (_managerObject.hasAttr("changeIcon")) {
Py::Callable libraries(_managerObject.getAttr("changeIcon"));
Py::Tuple args(2);
args.setItem(0, Py::String(libraryName.toStdString()));
args.setItem(1, Py::String(icon.toStdString()));
libraries.apply(args); // No return expected
}
else {
Base::Console().Log("\tchangeIcon() not found\n");
throw ConnectionError();
}
}
catch (Py::Exception& e) {
Base::PyException e1; // extract the Python error text
throw ReplacementError(e1.what());
}
}
void ExternalManager::removeLibrary(const QString& libraryName)
{
connect();
Base::PyGILStateLocker lock;
try {
if (_managerObject.hasAttr("removeLibrary")) {
Py::Callable libraries(_managerObject.getAttr("removeLibrary"));
Py::Tuple args(1);
args.setItem(0, Py::String(libraryName.toStdString()));
libraries.apply(args); // No return expected
}
else {
Base::Console().Log("\tremoveLibrary() not found\n");
throw ConnectionError();
}
}
catch (Py::Exception& e) {
Base::PyException e1; // extract the Python error text
throw DeleteError(e1.what());
}
}
std::shared_ptr<std::vector<std::tuple<QString, QString, QString>>>
ExternalManager::libraryModels(const QString& libraryName)
{
auto modelList = std::make_shared<std::vector<std::tuple<QString, QString, QString>>>();
connect();
Base::PyGILStateLocker lock;
try {
if (_managerObject.hasAttr("libraryModels")) {
Py::Callable libraries(_managerObject.getAttr("libraryModels"));
Py::Tuple args(1);
args.setItem(0, Py::String(libraryName.toStdString()));
Py::List list(libraries.apply(args));
for (auto library : list) {
auto entry = Py::Object(library);
if (!checkMaterialObjectType(entry)) {
throw InvalidModel();
}
modelList->push_back(materialObjectTypeFromObject(entry));
}
}
else {
Base::Console().Log("\tlibraryModels() not found\n");
throw ConnectionError();
}
}
catch (Py::Exception& e) {
Base::PyException e1; // extract the Python error text
throw LibraryNotFound(e1.what());
}
return modelList;
}
std::shared_ptr<std::vector<std::tuple<QString, QString, QString>>>
ExternalManager::libraryMaterials(const QString& libraryName)
{
auto materialList = std::make_shared<std::vector<std::tuple<QString, QString, QString>>>();
connect();
Base::PyGILStateLocker lock;
try {
if (_managerObject.hasAttr("libraryMaterials")) {
Py::Callable libraries(_managerObject.getAttr("libraryMaterials"));
Py::Tuple args(1);
args.setItem(0, Py::String(libraryName.toStdString()));
Py::List list(libraries.apply(args));
for (auto library : list) {
auto entry = Py::Object(library);
if (!checkMaterialObjectType(entry)) {
throw InvalidMaterial();
}
materialList->push_back(materialObjectTypeFromObject(entry));
}
}
else {
Base::Console().Log("\tlibraryMaterials() not found\n");
throw ConnectionError();
}
}
catch (Py::Exception& e) {
Base::PyException e1; // extract the Python error text
throw LibraryNotFound(e1.what());
}
return materialList;
}
std::shared_ptr<std::vector<std::tuple<QString, QString, QString>>>
ExternalManager::libraryMaterials(const QString& libraryName,
const std::shared_ptr<MaterialFilter>& filter,
const MaterialFilterOptions& options)
{
auto materialList = std::make_shared<std::vector<std::tuple<QString, QString, QString>>>();
connect();
Base::PyGILStateLocker lock;
try {
if (_managerObject.hasAttr("libraryMaterials")) {
Py::Callable libraries(_managerObject.getAttr("libraryMaterials"));
Py::Tuple args(3);
args.setItem(0, Py::String(libraryName.toStdString()));
if (filter) {
args.setItem(1,
Py::Object(new MaterialFilterPy(new MaterialFilter(*filter)), true));
}
else {
args.setItem(1, Py::None());
}
args.setItem(
2,
Py::Object(new MaterialFilterOptionsPy(new MaterialFilterOptions(options)), true));
Py::List list(libraries.apply(args));
for (auto library : list) {
auto entry = Py::Object(library);
if (!checkMaterialObjectType(entry)) {
throw InvalidMaterial();
}
materialList->push_back(materialObjectTypeFromObject(entry));
}
}
else {
Base::Console().Log("\tlibraryMaterials() not found\n");
throw ConnectionError();
}
}
catch (Py::Exception& e) {
Base::PyException e1; // extract the Python error text
throw LibraryNotFound(e1.what());
}
return materialList;
}
//=====
//
// Model management
//
//=====
std::shared_ptr<Model> ExternalManager::getModel(const QString& uuid)
{
connect();
Base::PyGILStateLocker lock;
try {
if (_managerObject.hasAttr("getModel")) {
Py::Callable libraries(_managerObject.getAttr("getModel"));
Py::Tuple args(1);
args.setItem(0, Py::String(uuid.toStdString()));
Py::Tuple result(libraries.apply(args)); // ignore return for now
Py::Object uuidObject = result.getItem(0);
Py::Tuple libraryObject(result.getItem(1));
Py::Object modelObject = result.getItem(2);
Py::Object pyName = libraryObject.getItem(0);
Py::Object pyIcon = libraryObject.getItem(1);
Py::Object readOnly = libraryObject.getItem(2);
QString name;
if (!pyName.isNone()) {
name = QString::fromStdString(pyName.as_string());
}
QString icon;
if (!pyIcon.isNone()) {
icon = QString::fromStdString(pyIcon.as_string());
}
auto library =
std::make_shared<ModelLibrary>(name, QString(), icon, readOnly.as_bool());
Model* model = static_cast<ModelPy*>(*modelObject)->getModelPtr();
model->setUUID(uuid);
model->setLibrary(library);
auto shared = std::make_shared<Model>(*model);
return shared;
}
else {
Base::Console().Log("\tgetModel() not found\n");
throw ConnectionError();
}
}
catch (Py::Exception& e) {
Base::PyException e1; // extract the Python error text
throw ModelNotFound(e1.what());
}
}
void ExternalManager::addModel(const QString& libraryName,
const QString& path,
const std::shared_ptr<Model>& model)
{
connect();
Base::PyGILStateLocker lock;
try {
if (_managerObject.hasAttr("addModel")) {
Py::Callable libraries(_managerObject.getAttr("addModel"));
Py::Tuple args(3);
args.setItem(0, Py::String(libraryName.toStdString()));
args.setItem(1, Py::String(path.toStdString()));
args.setItem(2, Py::Object(new ModelPy(new Model(*model)), true));
libraries.apply(args); // No return expected
}
else {
Base::Console().Log("\taddModel() not found\n");
throw ConnectionError();
}
}
catch (Py::Exception& e) {
Base::PyException e1; // extract the Python error text
throw CreationError(e1.what());
}
}
void ExternalManager::migrateModel(const QString& libraryName,
const QString& path,
const std::shared_ptr<Model>& model)
{
connect();
Base::PyGILStateLocker lock;
try {
if (_managerObject.hasAttr("migrateModel")) {
Py::Callable libraries(_managerObject.getAttr("migrateModel"));
Py::Tuple args(3);
args.setItem(0, Py::String(libraryName.toStdString()));
args.setItem(1, Py::String(path.toStdString()));
args.setItem(2, Py::Object(new ModelPy(new Model(*model)), true));
libraries.apply(args); // No return expected
}
else {
Base::Console().Log("\tmigrateModel() not found\n");
throw ConnectionError();
}
}
catch (Py::Exception& e) {
Base::PyException e1; // extract the Python error text
throw CreationError(e1.what());
}
}
//=====
//
// Material management
//
//=====
std::shared_ptr<Material> ExternalManager::getMaterial(const QString& uuid)
{
connect();
Base::PyGILStateLocker lock;
try {
if (_managerObject.hasAttr("getMaterial")) {
Py::Callable libraries(_managerObject.getAttr("getMaterial"));
Py::Tuple args(1);
args.setItem(0, Py::String(uuid.toStdString()));
Py::Tuple result(libraries.apply(args));
Py::Object uuidObject = result.getItem(0);
Py::Tuple libraryObject(result.getItem(1));
Py::Object materialObject = result.getItem(2);
Py::Object pyName = libraryObject.getItem(0);
Py::Object pyIcon = libraryObject.getItem(1);
Py::Object readOnly = libraryObject.getItem(2);
QString name;
if (!pyName.isNone()) {
name = QString::fromStdString(pyName.as_string());
}
QString icon;
if (!pyIcon.isNone()) {
icon = QString::fromStdString(pyIcon.as_string());
}
auto library =
std::make_shared<MaterialLibrary>(name, QString(), icon, readOnly.as_bool());
Material* material = static_cast<MaterialPy*>(*materialObject)->getMaterialPtr();
material->setUUID(uuid);
material->setLibrary(library);
auto shared = std::make_shared<Material>(*material);
return shared;
}
else {
Base::Console().Log("\tgetMaterial() not found\n");
throw ConnectionError();
}
}
catch (Py::Exception& e) {
Base::PyException e1; // extract the Python error text
throw MaterialNotFound(e1.what());
}
}
void ExternalManager::addMaterial(const QString& libraryName,
const QString& path,
const std::shared_ptr<Material>& material)
{
connect();
Base::PyGILStateLocker lock;
try {
if (_managerObject.hasAttr("addMaterial")) {
Py::Callable libraries(_managerObject.getAttr("addMaterial"));
Py::Tuple args(3);
args.setItem(0, Py::String(libraryName.toStdString()));
args.setItem(1, Py::String(path.toStdString()));
args.setItem(2, Py::Object(new MaterialPy(new Material(*material)), true));
libraries.apply(args); // No return expected
}
else {
Base::Console().Log("\taddMaterial() not found\n");
throw ConnectionError();
}
}
catch (Py::Exception& e) {
Base::PyException e1; // extract the Python error text
throw CreationError(e1.what());
}
}
void ExternalManager::migrateMaterial(const QString& libraryName,
const QString& path,
const std::shared_ptr<Material>& material)
{
connect();
Base::PyGILStateLocker lock;
try {
if (_managerObject.hasAttr("migrateMaterial")) {
Py::Callable libraries(_managerObject.getAttr("migrateMaterial"));
Py::Tuple args(3);
args.setItem(0, Py::String(libraryName.toStdString()));
args.setItem(1, Py::String(path.toStdString()));
auto mat = new Material(*material);
args.setItem(2, Py::Object(new MaterialPy(mat), true));
libraries.apply(args); // No return expected
}
else {
Base::Console().Log("\tmigrateMaterial() not found\n");
throw ConnectionError();
}
}
catch (Py::Exception& e) {
Base::PyException e1; // extract the Python error text
throw CreationError(e1.what());
}
}

View File

@@ -0,0 +1,112 @@
/***************************************************************************
* Copyright (c) 2024 David Carter <dcarter@david.carter.ca> *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
**************************************************************************/
#ifndef MATERIAL_EXTERNALMANAGER_H
#define MATERIAL_EXTERNALMANAGER_H
#include <Base/Parameter.h>
#include <CXX/Objects.hxx>
#include <Mod/Material/MaterialGlobal.h>
class QMutex;
class QString;
namespace Materials
{
class Library;
class Material;
class Model;
class MaterialFilter;
class MaterialFilterOptions;
class MaterialsExport ExternalManager: public ParameterGrp::ObserverType
{
public:
static ExternalManager* getManager();
/// Observer message from the ParameterGrp
void OnChange(ParameterGrp::SubjectType& rCaller, ParameterGrp::MessageType Reason) override;
// Library management
std::shared_ptr<std::vector<std::shared_ptr<Library>>> libraries();
std::shared_ptr<std::vector<std::shared_ptr<Library>>> modelLibraries();
std::shared_ptr<std::vector<std::shared_ptr<Library>>> materialLibraries();
std::shared_ptr<Library> getLibrary(const QString& name);
void createLibrary(const QString& libraryName, const QString& icon, bool readOnly = true);
void renameLibrary(const QString& libraryName, const QString& newName);
void changeIcon(const QString& libraryName, const QString& icon);
void removeLibrary(const QString& libraryName);
std::shared_ptr<std::vector<std::tuple<QString, QString, QString>>>
libraryModels(const QString& libraryName);
std::shared_ptr<std::vector<std::tuple<QString, QString, QString>>>
libraryMaterials(const QString& libraryName);
std::shared_ptr<std::vector<std::tuple<QString, QString, QString>>>
libraryMaterials(const QString& libraryName,
const std::shared_ptr<MaterialFilter>& filter,
const MaterialFilterOptions& options);
// Model management
std::shared_ptr<Model> getModel(const QString& uuid);
void
addModel(const QString& libraryName, const QString& path, const std::shared_ptr<Model>& model);
void
migrateModel(const QString& libraryName, const QString& path, const std::shared_ptr<Model>& model);
// Material management
std::shared_ptr<Material> getMaterial(const QString& uuid);
void addMaterial(const QString& libraryName,
const QString& path,
const std::shared_ptr<Material>& material);
void migrateMaterial(const QString& libraryName,
const QString& path,
const std::shared_ptr<Material>& material);
private:
ExternalManager();
~ExternalManager() override;
static void initManager();
void getConfiguration();
void instantiate();
void connect();
bool checkMaterialLibraryType(const Py::Object& entry);
std::shared_ptr<Library> libraryFromObject(const Py::Object& entry);
bool checkMaterialObjectType(const Py::Object& entry);
std::tuple<QString, QString, QString> materialObjectTypeFromObject(const Py::Object& entry);
static ExternalManager* _manager;
static QMutex _mutex;
// COnfiguration
ParameterGrp::handle _hGrp;
std::string _moduleName;
std::string _className;
bool _instantiated;
Py::Object _managerObject;
};
} // namespace Materials
#endif // MATERIAL_EXTERNALMANAGER_H

View File

@@ -40,6 +40,16 @@ Library::Library(const QString& libraryName, const QString& icon, bool readOnly)
, _readOnly(readOnly)
{}
Library::Library(const QString& libraryName,
const QString& icon,
bool readOnly,
const QString& timestamp)
: _name(libraryName)
, _iconPath(icon)
, _readOnly(readOnly)
, _timestamp(timestamp)
{}
Library::Library(const QString& libraryName, const QString& dir, const QString& icon, bool readOnly)
: _name(libraryName)
, _directory(QDir::cleanPath(dir))

View File

@@ -39,6 +39,10 @@ class MaterialsExport Library: public Base::BaseClass
public:
Library() = default;
Library(const QString& libraryName, const QString& icon, bool readOnly = true);
Library(const QString& libraryName,
const QString& icon,
bool readOnly,
const QString& timestamp);
Library(const QString& libraryName,
const QString& dir,
const QString& icon,
@@ -53,7 +57,7 @@ public:
{
_name = newName;
}
bool sameName(const QString& name)
bool isName(const QString& name)
{
return (_name == name);
}
@@ -87,6 +91,14 @@ public:
{
return QDir(_directory).absolutePath();
}
QString getTimestamp() const
{
return _timestamp;
}
void setTimestamp(const QString& timestamp)
{
_timestamp = timestamp;
}
bool operator==(const Library& library) const;
bool operator!=(const Library& library) const
@@ -107,6 +119,7 @@ private:
QString _directory;
QString _iconPath;
bool _readOnly;
QString _timestamp;
};
} // namespace Materials

View File

@@ -0,0 +1,249 @@
# ***************************************************************************
# * Copyright (c) 2024 David Carter <dcarter@davidcarter.ca> *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
__author__ = "David Carter"
__url__ = "https://www.davesrocketshop.com"
from abc import ABC, abstractmethod
from dataclasses import dataclass
import Materials
@dataclass
class MaterialLibraryType:
name: str
icon: bytes
readOnly: bool
timestamp: str
@dataclass
class MaterialLibraryObjectType:
UUID: str
path: str
name: str
class MaterialManagerExternal(ABC):
"""Abstract base class for all external material managers
Any external interface should be derivedfrom this base class."""
@classmethod
def APIVersion(cls) -> tuple:
"""Returns a tuple of 3 integers describing the API version
The version returned should be the latest supported version. This method
allows the interface to use older modules."""
return (1, 0, 0)
#
# Library methods
#
@abstractmethod
def libraries(self) -> list[MaterialLibraryType]:
"""Returns a list of libraries managed by this interface
The list contains a series of tuples describing all libraries managed by
this module. Each tuple containes the library name, icon, a boolean to indicate
if it is a read only library, and a timestamp that indicates when it was last
modified."""
pass
@abstractmethod
def modelLibraries(self) -> list[MaterialLibraryType]:
"""Returns a list of libraries managed by this interface
The list contains a series of tuples describing all libraries managed by
this module. Each tuple containes the library name, icon, and a boolean to indicate
if it is a read only library, and a timestamp that indicates when it was last
modified.
This differs from the libraries() function in that it only returns libraries
containing model objects."""
pass
@abstractmethod
def materialLibraries(self) -> list[MaterialLibraryType]:
"""Returns a list of libraries managed by this interface
The list contains a series of tuples describing all libraries managed by
this module. Each tuple containes the library name, icon, and a boolean to indicate
if it is a read only library, and a timestamp that indicates when it was last
modified.
This differs from the libraries() function in that it only returns libraries
containing material objects."""
pass
@abstractmethod
def getLibrary(self, name: str) -> tuple:
"""Get the library
Retrieve the library with the given name"""
pass
@abstractmethod
def createLibrary(self, name: str, icon: bytes, readOnly: bool) -> None:
"""Create a new library
Create a new library with the given name"""
pass
@abstractmethod
def renameLibrary(self, oldName: str, newName: str) -> None:
"""Rename an existing library
Change the name of an existing library"""
pass
@abstractmethod
def changeIcon(self, name: str, icon: bytes) -> None:
"""Change the library icon
Change the library icon"""
pass
@abstractmethod
def removeLibrary(self, library: str) -> None:
"""Delete a library and its contents
Deletes the library and any models or materials it contains"""
pass
@abstractmethod
def libraryModels(self, library: str) -> list[MaterialLibraryObjectType]:
"""Returns a list of models managed by this library
Each list entry is a tuple containing the UUID, path, and name of the model"""
pass
@abstractmethod
def libraryMaterials(self, library: str,
filter: Materials.MaterialFilter = None,
options: Materials.MaterialFilterOptions = None) -> list[MaterialLibraryObjectType]:
"""Returns a list of materials managed by this library
Each list entry is a tuple containing the UUID, path, and name of the material"""
pass
#
# Model methods
#
@abstractmethod
def getModel(self, uuid: str) -> Materials.Model:
"""Retrieve a model given its UUID"""
pass
@abstractmethod
def addModel(self, library: str, path: str, model: Materials.Model) -> None:
"""Add a model to a library in the given folder.
This will throw a DatabaseModelExistsError exception if the model already exists."""
pass
@abstractmethod
def migrateModel(self, library: str, path: str, model: Materials.Model) -> None:
"""Add the model to the library.
If the model already exists, then no action is performed."""
pass
@abstractmethod
def updateModel(self, library: str, path: str, model: Materials.Model) -> None:
"""Update the given model"""
pass
@abstractmethod
def setModelPath(self, library: str, path: str, model: Materials.Model) -> None:
"""Change the model path within the library"""
pass
@abstractmethod
def renameModel(self, library: str, name: str, model: Materials.Model) -> None:
"""Change the model name"""
pass
@abstractmethod
def moveModel(self, library: str, path: str, model: Materials.Model) -> None:
"""Move a model across libraries
Move the model to the desired path in a different library. This should also
remove the model from the old library if that library is managed by this
interface"""
pass
@abstractmethod
def removeModel(self, model: Materials.Model) -> None:
"""Remove the model from the library"""
pass
#
# Material methods
#
@abstractmethod
def getMaterial(self, uuid: str) -> Materials.Material:
""" Retrieve a material given its UUID """
pass
@abstractmethod
def addMaterial(self, library: str, path: str, material: Materials.Material) -> None:
"""Add a material to a library in the given folder.
This will throw a DatabaseMaterialExistsError exception if the model already exists."""
pass
@abstractmethod
def migrateMaterial(self, library: str, path: str, material: Materials.Material) -> None:
"""Add the material to the library in the given folder.
If the material already exists, then no action is performed."""
pass
@abstractmethod
def updateMaterial(self, library: str, path: str, material: Materials.Material) -> None:
"""Update the given material"""
pass
@abstractmethod
def setMaterialPath(self, library: str, path: str, material: Materials.Material) -> None:
"""Change the material path within the library"""
pass
@abstractmethod
def renameMaterial(self, library: str, name: str, material: Materials.Material) -> None:
"""Change the material name"""
pass
@abstractmethod
def moveMaterial(self, library: str, path: str, material: Materials.Material) -> None:
"""Move a material across libraries
Move the material to the desired path in a different library. This should also
remove the material from the old library if that library is managed by this
interface"""
pass
@abstractmethod
def removeMaterial(self, material: Materials.Material) -> None:
"""Remove the material from the library"""
pass

View File

@@ -130,7 +130,7 @@ MaterialManagerLocal::getMaterialLibraries()
std::shared_ptr<MaterialLibrary> MaterialManagerLocal::getLibrary(const QString& name) const
{
for (auto& library : *_libraryList) {
if (library->isLocal() && library->sameName(name)) {
if (library->isLocal() && library->isName(name)) {
return library;
}
}
@@ -160,7 +160,7 @@ void MaterialManagerLocal::createLibrary(const QString& libraryName,
void MaterialManagerLocal::renameLibrary(const QString& libraryName, const QString& newName)
{
for (auto& library : *_libraryList) {
if (library->isLocal() && library->sameName(libraryName)) {
if (library->isLocal() && library->isName(libraryName)) {
auto materialLibrary =
reinterpret_cast<const std::shared_ptr<Materials::MaterialLibraryLocal>&>(library);
materialLibrary->setName(newName);
@@ -174,7 +174,7 @@ void MaterialManagerLocal::renameLibrary(const QString& libraryName, const QStri
void MaterialManagerLocal::changeIcon(const QString& libraryName, const QString& icon)
{
for (auto& library : *_libraryList) {
if (library->isLocal() && library->sameName(libraryName)) {
if (library->isLocal() && library->isName(libraryName)) {
auto materialLibrary =
reinterpret_cast<const std::shared_ptr<Materials::MaterialLibraryLocal>&>(library);
materialLibrary->setIconPath(icon);
@@ -188,7 +188,7 @@ void MaterialManagerLocal::changeIcon(const QString& libraryName, const QString&
void MaterialManagerLocal::removeLibrary(const QString& libraryName)
{
for (auto& library : *_libraryList) {
if (library->isLocal() && library->sameName(libraryName)) {
if (library->isLocal() && library->isName(libraryName)) {
_libraryList->remove(library);
// At this point we should rebuild the material map
@@ -207,7 +207,7 @@ MaterialManagerLocal::libraryMaterials(const QString& libraryName)
for (auto& it : *_materialMap) {
// This is needed to resolve cyclic dependencies
auto library = it.second->getLibrary();
if (library->sameName(libraryName)) {
if (library->isName(libraryName)) {
materials->push_back(std::tuple<QString, QString, QString>(it.first,
it.second->getDirectory(),
it.second->getName()));
@@ -245,7 +245,7 @@ MaterialManagerLocal::libraryMaterials(const QString& libraryName,
for (auto& it : *_materialMap) {
// This is needed to resolve cyclic dependencies
auto library = it.second->getLibrary();
if (library->sameName(libraryName)) {
if (library->isName(libraryName)) {
if (passFilter(it.second, filter, options)) {
materials->push_back(std::tuple<QString, QString, QString>(it.first,
it.second->getDirectory(),