Material: Material appearance

Uses new material system for appearance

Each feature object now has a property called ShapeMaterial that
describes its physical properties. If it has a shape, it has a
material.

The ShapeColor attribute is replaced by a ShapeAppearance attribute.
This is a material list that describes all appearance properties, not
just diffuse color. As a list in can be used for all elements of a
shape, such as edges and faces.

A new widget is provided to allow the user to select materials in a
consistent fashion. It can also launch the material editor with its
more advanced capabilities.
This commit is contained in:
David Carter
2024-03-17 18:37:56 -04:00
committed by Chris Hennes
parent 37c38acd19
commit ba20441935
121 changed files with 4682 additions and 1685 deletions

View File

@@ -36,6 +36,7 @@
#include "ModelPropertyPy.h"
#include "ModelPy.h"
#include "UUIDsPy.h"
#include "PropertyMaterial.h"
namespace Materials
{
@@ -43,9 +44,9 @@ class Module: public Py::ExtensionModule<Module>
{
public:
Module()
: Py::ExtensionModule<Module>("Material")
: Py::ExtensionModule<Module>("Materials")
{
initialize("This module is the Material module."); // register with Python
initialize("This module is the Materials module."); // register with Python
}
~Module() override = default;
@@ -60,18 +61,18 @@ PyObject* initModule()
} // namespace Materials
PyMOD_INIT_FUNC(Material)
PyMOD_INIT_FUNC(Materials)
{
PyObject* module = Materials::initModule();
Base::Console().Log("Loading Material module... done\n");
Base::Interpreter().addType(&Materials::MaterialManagerPy ::Type, module, "MaterialManager");
Base::Interpreter().addType(&Materials::MaterialPy ::Type, module, "Material");
Base::Interpreter().addType(&Materials::ModelManagerPy ::Type, module, "ModelManager");
Base::Interpreter().addType(&Materials::ModelPropertyPy ::Type, module, "ModelProperty");
Base::Interpreter().addType(&Materials::ModelPy ::Type, module, "Model");
Base::Interpreter().addType(&Materials::UUIDsPy ::Type, module, "UUIDs");
Base::Interpreter().addType(&Materials::MaterialManagerPy::Type, module, "MaterialManager");
Base::Interpreter().addType(&Materials::MaterialPy::Type, module, "Material");
Base::Interpreter().addType(&Materials::ModelManagerPy::Type, module, "ModelManager");
Base::Interpreter().addType(&Materials::ModelPropertyPy::Type, module, "ModelProperty");
Base::Interpreter().addType(&Materials::ModelPy::Type, module, "Model");
Base::Interpreter().addType(&Materials::UUIDsPy::Type, module, "UUIDs");
// Initialize types
@@ -95,5 +96,7 @@ PyMOD_INIT_FUNC(Material)
Materials::Material2DArray ::init();
Materials::Material3DArray ::init();
Materials::PropertyMaterial ::init();
PyMOD_Return(module);
}

View File

@@ -20,7 +20,7 @@ include_directories(
)
link_directories(${YAML_CPP_LIBRARY_DIR})
set(Material_LIBS
set(Materials_LIBS
${Boost_LIBRARIES}
FreeCADApp
)
@@ -28,22 +28,23 @@ set(Material_LIBS
include_directories(
${QtConcurrent_INCLUDE_DIRS}
)
list(APPEND Material_LIBS
list(APPEND Materials_LIBS
${QtConcurrent_LIBRARIES}
)
if(yaml-cpp_VERSION VERSION_LESS 0.8.0)
list(APPEND Material_LIBS
list(APPEND Materials_LIBS
${YAML_CPP_LIBRARIES}
)
else()
list(APPEND Material_LIBS
list(APPEND Materials_LIBS
yaml-cpp::yaml-cpp
)
endif()
generate_from_xml(Array2DPy)
generate_from_xml(Array3DPy)
generate_from_xml(MaterialFilterPy)
generate_from_xml(MaterialManagerPy)
generate_from_xml(MaterialPy)
generate_from_xml(ModelManagerPy)
@@ -61,6 +62,8 @@ SET(Python_SRCS
MaterialManagerPyImpl.cpp
MaterialPy.xml
MaterialPyImpl.cpp
MaterialFilterPy.xml
MaterialFilterPyImpl.cpp
ModelManagerPy.xml
ModelManagerPyImpl.cpp
ModelPropertyPy.xml
@@ -72,7 +75,7 @@ SET(Python_SRCS
)
SOURCE_GROUP("Python" FILES ${Python_SRCS})
SET(Material_SRCS
SET(Materials_SRCS
${Python_SRCS}
AppMaterial.cpp
FolderTree.h
@@ -102,19 +105,21 @@ SET(Material_SRCS
ModelUuids.h
PreCompiled.cpp
PreCompiled.h
PropertyMaterial.cpp
PropertyMaterial.h
trim.h
)
if(FREECAD_USE_PCH)
add_definitions(-D_PreComp_)
GET_MSVC_PRECOMPILED_SOURCE("PreCompiled.cpp" PCH_SRCS ${Material_SRCS})
ADD_MSVC_PRECOMPILED_HEADER(Material PreCompiled.h PreCompiled.cpp PCH_SRCS)
GET_MSVC_PRECOMPILED_SOURCE("PreCompiled.cpp" PCH_SRCS ${Materials_SRCS})
ADD_MSVC_PRECOMPILED_HEADER(Materials PreCompiled.h PreCompiled.cpp PCH_SRCS)
endif(FREECAD_USE_PCH)
add_library(Material SHARED ${Material_SRCS})
target_link_libraries(Material ${Material_LIBS})
add_library(Materials SHARED ${Materials_SRCS})
target_link_libraries(Materials ${Materials_LIBS})
SET_BIN_DIR(Material Material /Mod/Material)
SET_PYTHON_PREFIX_SUFFIX(Material)
SET_BIN_DIR(Materials Materials /Mod/Material)
SET_PYTHON_PREFIX_SUFFIX(Materials)
INSTALL(TARGETS Material DESTINATION ${CMAKE_INSTALL_LIBDIR})
INSTALL(TARGETS Materials DESTINATION ${CMAKE_INSTALL_LIBDIR})

View File

@@ -1,5 +1,5 @@
/***************************************************************************
* Copyright (c) 2023 David Carter <dcarter@david.carter.ca> *
* Copyright (c) 2023-2024 David Carter <dcarter@david.carter.ca> *
* *
* This file is part of FreeCAD. *
* *
@@ -25,7 +25,9 @@
#include <App/Application.h>
#include "Exceptions.h"
#include "MaterialFilter.h"
#include "MaterialManager.h"
#include "Materials.h"
@@ -56,6 +58,18 @@ bool MaterialFilter::modelIncluded(const std::shared_ptr<Material>& material) co
return true;
}
bool MaterialFilter::modelIncluded(const QString& uuid) const
{
MaterialManager manager;
try {
auto material = manager.getMaterial(uuid);
return modelIncluded(material);
}
catch (const MaterialNotFound&) {
}
return false;
}
void MaterialFilter::addRequired(const QString& uuid)
{
// Ignore any uuids already present

View File

@@ -1,5 +1,5 @@
/***************************************************************************
* Copyright (c) 2023 David Carter <dcarter@david.carter.ca> *
* Copyright (c) 2023-2024 David Carter <dcarter@david.carter.ca> *
* *
* This file is part of FreeCAD. *
* *
@@ -56,9 +56,9 @@ public:
{
return _includeFolders;
}
void setIncludeEmptyFolders(bool include)
void setIncludeEmptyFolders(bool value)
{
_includeFolders = include;
_includeFolders = value;
}
/* Indicates if we should include materials in the older format
@@ -80,10 +80,26 @@ public:
* Models only need to be included in one set.
*/
bool modelIncluded(const std::shared_ptr<Material>& material) const;
bool modelIncluded(const QString& uuid) const;
/* Add model UUIDs for required models, or models that are both required
* and complete.
*/
void addRequired(const QString& uuid);
void addRequiredComplete(const QString& uuid);
/* These functions shouldn't normally be called directly. They are
* for use by conversion methods, such as MaterialFilterPy
*/
const QSet<QString>* getRequired() const
{
return &_required;
}
const QSet<QString>* getRequiredComplete() const
{
return &_requiredComplete;
}
private:
bool _includeFolders;
bool _includeLegacy;

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<GenerateModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="generateMetaModel_Module.xsd">
<PythonExport
Father="BaseClassPy"
Name="MaterialFilterPy"
Twin="MaterialFilter"
TwinPointer="MaterialFilter"
Include="Mod/Material/App/MaterialFilter.h"
Namespace="Materials"
FatherInclude="Base/BaseClassPy.h"
FatherNamespace="Base"
Constructor="true"
Delete="true">
<Documentation>
<Author Licence="LGPL" Name="DavidCarter" EMail="dcarter@davidcarter.ca" />
<UserDocu>Material filters.</UserDocu>
</Documentation>
<Attribute Name="IncludeEmptyFolders" ReadOnly="false">
<Documentation>
<UserDocu>Include empty folders in the material list.</UserDocu>
</Documentation>
<Parameter Name="IncludeEmptyFolders" Type="Boolean"/>
</Attribute>
<Attribute Name="IncludeLegacy" ReadOnly="false">
<Documentation>
<UserDocu>Include legacy materials in the material list.</UserDocu>
</Documentation>
<Parameter Name="IncludeLegacy" Type="Boolean"/>
</Attribute>
<Attribute Name="RequiredModels" ReadOnly="false">
<Documentation>
<UserDocu>Materials must include the specified models.</UserDocu>
</Documentation>
<Parameter Name="RequiredModels" Type="List"/>
</Attribute>
<Attribute Name="RequiredCompleteModels" ReadOnly="false">
<Documentation>
<UserDocu>Materials must have complete versions of the specified models.</UserDocu>
</Documentation>
<Parameter Name="RequiredCompleteModels" Type="List"/>
</Attribute>
</PythonExport>
</GenerateModel>

View File

@@ -0,0 +1,132 @@
/***************************************************************************
* Copyright (c) 2023-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"
#include <QMetaType>
#include <Base/Quantity.h>
#include <Base/QuantityPy.h>
#include <CXX/Objects.hxx>
#include <Gui/MetaTypes.h>
#include "MaterialFilter.h"
#include "MaterialFilterPy.h"
#include "MaterialFilterPy.cpp"
using namespace Materials;
// Forward declaration
// static PyObject* _pyObjectFromVariant(const QVariant& value);
// static Py::List getList(const QVariant& value);
// returns a string which represents the object e.g. when printed in python
std::string MaterialFilterPy::representation() const
{
std::stringstream str;
str << "<MaterialFilter object at " << getMaterialFilterPtr() << ">";
return str.str();
}
PyObject* MaterialFilterPy::PyMake(struct _typeobject*, PyObject*, PyObject*) // Python wrapper
{
// never create such objects with the constructor
return new MaterialFilterPy(new MaterialFilter());
}
// constructor method
int MaterialFilterPy::PyInit(PyObject* /*args*/, PyObject* /*kwd*/)
{
return 0;
}
Py::Boolean MaterialFilterPy::getIncludeEmptyFolders() const
{
return {getMaterialFilterPtr()->includeEmptyFolders()};
}
void MaterialFilterPy::setIncludeEmptyFolders(const Py::Boolean value)
{
getMaterialFilterPtr()->setIncludeEmptyFolders(value.isTrue());
}
Py::Boolean MaterialFilterPy::getIncludeLegacy() const
{
return {getMaterialFilterPtr()->includeLegacy()};
}
void MaterialFilterPy::setIncludeLegacy(const Py::Boolean value)
{
getMaterialFilterPtr()->setIncludeLegacy(value.isTrue());
}
Py::List MaterialFilterPy::getRequiredModels() const
{
auto listValue = getMaterialFilterPtr()->getRequired();
Py::List list;
for (auto& it : *listValue) {
list.append(Py::String(it.toStdString()));
}
return list;
}
void MaterialFilterPy::setRequiredModels(Py::List value)
{
for (const auto& it : value) {
Py::String uuid(it);
getMaterialFilterPtr()->addRequired(QString::fromStdString(uuid));
}
}
Py::List MaterialFilterPy::getRequiredCompleteModels() const
{
auto listValue = getMaterialFilterPtr()->getRequiredComplete();
Py::List list;
for (auto& it : *listValue) {
list.append(Py::String(it.toStdString()));
}
return list;
}
void MaterialFilterPy::setRequiredCompleteModels(Py::List value)
{
for (const auto& it : value) {
Py::String uuid(it);
getMaterialFilterPtr()->addRequiredComplete(QString::fromStdString(uuid));
}
}
PyObject* MaterialFilterPy::getCustomAttributes(const char* /*attr*/) const
{
return nullptr;
}
int MaterialFilterPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/)
{
return 0;
}

View File

@@ -21,9 +21,9 @@
#include "PreCompiled.h"
#ifndef _PreComp_
#include <QVector>
#include <QDirIterator>
#include <QFileInfo>
#include <QVector>
#endif
@@ -266,7 +266,7 @@ QString MaterialLibrary::getUUIDFromPath(const QString& path) const
}
bool MaterialLibrary::materialInTree(const std::shared_ptr<Material>& material,
const MaterialFilter* filter) const
const std::shared_ptr<Materials::MaterialFilter>& filter) const
{
if (!filter) {
// If there's no filter we always include
@@ -283,7 +283,7 @@ bool MaterialLibrary::materialInTree(const std::shared_ptr<Material>& material,
}
std::shared_ptr<std::map<QString, std::shared_ptr<MaterialTreeNode>>>
MaterialLibrary::getMaterialTree(const MaterialFilter* filter) const
MaterialLibrary::getMaterialTree(const std::shared_ptr<Materials::MaterialFilter>& filter) const
{
std::shared_ptr<std::map<QString, std::shared_ptr<MaterialTreeNode>>> materialTree =
std::make_shared<std::map<QString, std::shared_ptr<MaterialTreeNode>>>();

View File

@@ -79,7 +79,7 @@ public:
std::shared_ptr<Material> addMaterial(const std::shared_ptr<Material>& material,
const QString& path);
std::shared_ptr<std::map<QString, std::shared_ptr<MaterialTreeNode>>>
getMaterialTree(const MaterialFilter* filter = nullptr) const;
getMaterialTree(const std::shared_ptr<Materials::MaterialFilter>& filter) const;
bool isReadOnly() const
{
@@ -99,7 +99,7 @@ protected:
void updatePaths(const QString& oldPath, const QString& newPath);
QString getUUIDFromPath(const QString& path) const;
bool materialInTree(const std::shared_ptr<Material>& material,
const MaterialFilter* filter) const;
const std::shared_ptr<Materials::MaterialFilter>& filter) const;
bool _readOnly;
std::unique_ptr<std::map<QString, std::shared_ptr<Material>>> _materialPathMap;

View File

@@ -27,12 +27,14 @@
#include <QMutexLocker>
#include <App/Application.h>
#include <App/Material.h>
#include "Exceptions.h"
#include "MaterialConfigLoader.h"
#include "MaterialLoader.h"
#include "MaterialManager.h"
#include "ModelManager.h"
#include "ModelUuids.h"
using namespace Materials;
@@ -108,6 +110,47 @@ bool MaterialManager::isMaterial(const QFileInfo& file) const
return false;
}
std::shared_ptr<Material> MaterialManager::defaultMaterial()
{
MaterialManager manager;
ParameterGrp::handle hGrp =
App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View");
bool randomColor = hGrp->GetBool("RandomColor", false);
float r, g, b;
if (randomColor) {
auto fMax = (float)RAND_MAX;
r = (float)rand() / fMax;
g = (float)rand() / fMax;
b = (float)rand() / fMax;
}
else {
unsigned long shcol = hGrp->GetUnsigned("DefaultShapeColor", 3435980543UL);
r = ((shcol >> 24) & 0xff) / 255.0;
g = ((shcol >> 16) & 0xff) / 255.0;
b = ((shcol >> 8) & 0xff) / 255.0;
}
int initialTransparency = hGrp->GetInt("DefaultShapeTransparency", 0);
auto material = manager.getMaterial(defaultMaterialUUID());
if (material->hasAppearanceModel(ModelUUIDs::ModelUUID_Rendering_Basic)) {
material->getAppearanceProperty(QString::fromLatin1("DiffuseColor"))
->setColor(App::Color(r, g, b));
material->getAppearanceProperty(QString::fromLatin1("Transparency"))
->setFloat((float)initialTransparency / 100.0f);
}
return material;
}
QString MaterialManager::defaultMaterialUUID()
{
// Make this a preference
return QString::fromLatin1("7f9fd73b-50c9-41d8-b7b2-575a030c1eeb");
}
std::shared_ptr<Material> MaterialManager::getMaterial(const QString& uuid) const
{
try {
@@ -118,6 +161,13 @@ std::shared_ptr<Material> MaterialManager::getMaterial(const QString& uuid) cons
}
}
std::shared_ptr<Material> MaterialManager::getMaterial(const App::Material& material)
{
MaterialManager manager;
return manager.getMaterial(QString::fromStdString(material.uuid));
}
std::shared_ptr<Material> MaterialManager::getMaterialByPath(const QString& path) const
{
QString cleanPath = QDir::cleanPath(path);
@@ -126,7 +176,9 @@ std::shared_ptr<Material> MaterialManager::getMaterialByPath(const QString& path
if (cleanPath.startsWith(library->getDirectory())) {
try {
return library->getMaterialByPath(cleanPath);
} catch (const MaterialNotFound&) {}
}
catch (const MaterialNotFound&) {
}
// See if it's a new file saved by the old editor
{

View File

@@ -37,6 +37,11 @@
namespace fs = boost::filesystem;
namespace App
{
class Material;
}
namespace Materials
{
@@ -50,11 +55,15 @@ public:
MaterialManager();
~MaterialManager() override = default;
static std::shared_ptr<Material> defaultMaterial();
static QString defaultMaterialUUID();
std::shared_ptr<std::map<QString, std::shared_ptr<Material>>> getMaterials() const
{
return _materialMap;
}
std::shared_ptr<Material> getMaterial(const QString& uuid) const;
static std::shared_ptr<Material> getMaterial(const App::Material& material);
std::shared_ptr<Material> getMaterialByPath(const QString& path) const;
std::shared_ptr<Material> getMaterialByPath(const QString& path, const QString& library) const;
std::shared_ptr<Material> getParent(const std::shared_ptr<Material>& material) const;
@@ -66,10 +75,16 @@ public:
std::shared_ptr<std::list<std::shared_ptr<MaterialLibrary>>> getMaterialLibraries() const;
std::shared_ptr<std::map<QString, std::shared_ptr<MaterialTreeNode>>>
getMaterialTree(const std::shared_ptr<MaterialLibrary>& library,
const MaterialFilter* filter = nullptr) const
const std::shared_ptr<Materials::MaterialFilter>& filter) const
{
return library->getMaterialTree(filter);
}
std::shared_ptr<std::map<QString, std::shared_ptr<MaterialTreeNode>>>
getMaterialTree(const std::shared_ptr<MaterialLibrary>& library) const
{
std::shared_ptr<Materials::MaterialFilter> filter;
return library->getMaterialTree(filter);
}
std::shared_ptr<std::list<QString>>
getMaterialFolders(const std::shared_ptr<MaterialLibrary>& library) const;
void createFolder(const std::shared_ptr<MaterialLibrary>& library, const QString& path) const

View File

@@ -103,69 +103,69 @@ int MaterialPy::PyInit(PyObject* /*args*/, PyObject* /*kwd*/)
Py::String MaterialPy::getLibraryName() const
{
auto library = getMaterialPtr()->getLibrary();
return Py::String(library ? library->getName().toStdString() : "");
return {library ? library->getName().toStdString() : ""};
}
Py::String MaterialPy::getLibraryRoot() const
{
auto library = getMaterialPtr()->getLibrary();
return Py::String(library ? library->getDirectoryPath().toStdString() : "");
return {library ? library->getDirectoryPath().toStdString() : ""};
}
Py::String MaterialPy::getLibraryIcon() const
{
auto library = getMaterialPtr()->getLibrary();
return Py::String(library ? library->getIconPath().toStdString() : "");
return {library ? library->getIconPath().toStdString() : ""};
}
Py::String MaterialPy::getName() const
{
return Py::String(getMaterialPtr()->getName().toStdString());
return {getMaterialPtr()->getName().toStdString()};
}
Py::String MaterialPy::getDirectory() const
{
return Py::String(getMaterialPtr()->getDirectory().toStdString());
return {getMaterialPtr()->getDirectory().toStdString()};
}
Py::String MaterialPy::getUUID() const
{
return Py::String(getMaterialPtr()->getUUID().toStdString());
return {getMaterialPtr()->getUUID().toStdString()};
}
Py::String MaterialPy::getDescription() const
{
return Py::String(getMaterialPtr()->getDescription().toStdString());
return {getMaterialPtr()->getDescription().toStdString()};
}
Py::String MaterialPy::getURL() const
{
return Py::String(getMaterialPtr()->getURL().toStdString());
return {getMaterialPtr()->getURL().toStdString()};
}
Py::String MaterialPy::getReference() const
{
return Py::String(getMaterialPtr()->getReference().toStdString());
return {getMaterialPtr()->getReference().toStdString()};
}
Py::String MaterialPy::getParent() const
{
return Py::String(getMaterialPtr()->getParentUUID().toStdString());
return {getMaterialPtr()->getParentUUID().toStdString()};
}
Py::String MaterialPy::getAuthorAndLicense() const
{
return Py::String(getMaterialPtr()->getAuthorAndLicense().toStdString());
return {getMaterialPtr()->getAuthorAndLicense().toStdString()};
}
Py::String MaterialPy::getAuthor() const
{
return Py::String(getMaterialPtr()->getAuthor().toStdString());
return {getMaterialPtr()->getAuthor().toStdString()};
}
Py::String MaterialPy::getLicense() const
{
return Py::String(getMaterialPtr()->getLicense().toStdString());
return {getMaterialPtr()->getLicense().toStdString()};
}
Py::List MaterialPy::getPhysicalModels() const
@@ -173,8 +173,8 @@ Py::List MaterialPy::getPhysicalModels() const
auto models = getMaterialPtr()->getPhysicalModels();
Py::List list;
for (auto it = models->begin(); it != models->end(); it++) {
list.append(Py::String(it->toStdString()));
for (auto it : *models) {
list.append(Py::String(it.toStdString()));
}
return list;
@@ -185,8 +185,8 @@ Py::List MaterialPy::getAppearanceModels() const
auto models = getMaterialPtr()->getAppearanceModels();
Py::List list;
for (auto it = models->begin(); it != models->end(); it++) {
list.append(Py::String(it->toStdString()));
for (auto it : *models) {
list.append(Py::String(it.toStdString()));
}
return list;
@@ -197,8 +197,8 @@ Py::List MaterialPy::getTags() const
auto& tags = getMaterialPtr()->getTags();
Py::List list;
for (auto it = tags.begin(); it != tags.end(); it++) {
list.append(Py::String(it->toStdString()));
for (auto it : tags) {
list.append(Py::String(it.toStdString()));
}
return list;
@@ -298,9 +298,9 @@ Py::Dict MaterialPy::getProperties() const
dict.setItem(Py::String("SourceURL"), Py::String(getMaterialPtr()->getURL().toStdString()));
auto properties = getMaterialPtr()->getPhysicalProperties();
for (auto it = properties.begin(); it != properties.end(); it++) {
QString key = it->first;
auto materialProperty = it->second;
for (auto& it : properties) {
QString key = it.first;
auto materialProperty = it.second;
if (!materialProperty->isNull()) {
auto value = materialProperty->getDictionaryString();
@@ -309,9 +309,9 @@ Py::Dict MaterialPy::getProperties() const
}
properties = getMaterialPtr()->getAppearanceProperties();
for (auto it = properties.begin(); it != properties.end(); it++) {
QString key = it->first;
auto materialProperty = it->second;
for (auto& it : properties) {
QString key = it.first;
auto materialProperty = it.second;
if (!materialProperty->isNull()) {
auto value = materialProperty->getDictionaryString();
@@ -327,9 +327,9 @@ Py::Dict MaterialPy::getPhysicalProperties() const
Py::Dict dict;
auto properties = getMaterialPtr()->getPhysicalProperties();
for (auto it = properties.begin(); it != properties.end(); it++) {
QString key = it->first;
auto materialProperty = it->second;
for (auto& it : properties) {
QString key = it.first;
auto materialProperty = it.second;
if (!materialProperty->isNull()) {
auto value = materialProperty->getDictionaryString();
@@ -345,9 +345,9 @@ Py::Dict MaterialPy::getAppearanceProperties() const
Py::Dict dict;
auto properties = getMaterialPtr()->getAppearanceProperties();
for (auto it = properties.begin(); it != properties.end(); it++) {
QString key = it->first;
auto materialProperty = it->second;
for (auto& it : properties) {
QString key = it.first;
auto materialProperty = it.second;
if (!materialProperty->isNull()) {
auto value = materialProperty->getDictionaryString();
@@ -425,7 +425,7 @@ PyObject* MaterialPy::getPhysicalValue(PyObject* args)
std::static_pointer_cast<Materials::Material2DArray>(property->getMaterialValue());
return new Array2DPy(new Material2DArray(*value));
}
else if (property->getType() == MaterialValue::Array3D) {
if (property->getType() == MaterialValue::Array3D) {
auto value =
std::static_pointer_cast<Materials::Material3DArray>(property->getMaterialValue());
return new Array3DPy(new Material3DArray(*value));

View File

@@ -34,6 +34,7 @@
#include "MaterialLibrary.h"
#include "MaterialManager.h"
#include "ModelManager.h"
#include "ModelUuids.h"
using namespace Materials;
@@ -142,6 +143,32 @@ QString MaterialProperty::getYAMLString() const
return _valuePtr->getYAMLString();
}
App::Color MaterialProperty::getColor() const
{
auto colorString = getValue().toString();
std::stringstream stream(colorString.toStdString());
char c;
stream >> c; // read "("
float red;
stream >> red;
stream >> c; // ","
float green;
stream >> green;
stream >> c; // ","
float blue;
stream >> blue;
stream >> c; // ","
float alpha = 1.0;
if (c == ',') {
stream >> alpha;
}
App::Color color(red, green, blue, alpha);
return color;
}
QString MaterialProperty::getDictionaryString() const
{
// This method produces a non-localized string. For a localized string use
@@ -377,6 +404,13 @@ void MaterialProperty::setURL(const QString& value)
_valuePtr->setValue(QVariant(value));
}
void MaterialProperty::setColor(const App::Color& value)
{
std::stringstream ss;
ss << "(" << value.r << ", " << value.g << ", " << value.b << ", " << value.a << ")";
_valuePtr->setValue(QVariant(QString::fromStdString(ss.str())));
}
MaterialProperty& MaterialProperty::operator=(const MaterialProperty& other)
{
if (this == &other) {
@@ -1434,6 +1468,23 @@ Material& Material::operator=(const Material& other)
return *this;
}
Material& Material::operator=(const App::Material& other)
{
if (!hasAppearanceModel(ModelUUIDs::ModelUUID_Rendering_Basic)) {
addAppearance(ModelUUIDs::ModelUUID_Rendering_Basic);
}
getAppearanceProperty(QString::fromLatin1("AmbientColor"))->setColor(other.ambientColor);
getAppearanceProperty(QString::fromLatin1("DiffuseColor"))->setColor(other.diffuseColor);
getAppearanceProperty(QString::fromLatin1("SpecularColor"))->setColor(other.specularColor);
getAppearanceProperty(QString::fromLatin1("EmissiveColor"))->setColor(other.emissiveColor);
getAppearanceProperty(QString::fromLatin1("Shininess"))->setFloat(other.shininess);
getAppearanceProperty(QString::fromLatin1("Transparency"))->setFloat(other.transparency);
// std::string uuid;
return *this;
}
/*
* Normalize models by removing any inherited models
*/
@@ -1509,3 +1560,50 @@ QStringList Material::inheritedAddedModels(const Material& parent) const
*/
void Material::inheritedPropertyDiff([[maybe_unused]] const QString& parent)
{}
/*
* Return an App::Material object describing the materials appearance, or DEFAULT if
* undefined.
*/
App::Material Material::getMaterialAppearance() const
{
App::Material material(App::Material::DEFAULT);
bool custom = false;
if (hasAppearanceProperty(QString::fromLatin1("AmbientColor"))) {
material.ambientColor =
getAppearanceProperty(QString::fromLatin1("AmbientColor"))->getColor();
custom = true;
}
if (hasAppearanceProperty(QString::fromLatin1("DiffuseColor"))) {
material.diffuseColor =
getAppearanceProperty(QString::fromLatin1("DiffuseColor"))->getColor();
custom = true;
}
if (hasAppearanceProperty(QString::fromLatin1("SpecularColor"))) {
material.specularColor =
getAppearanceProperty(QString::fromLatin1("SpecularColor"))->getColor();
custom = true;
}
if (hasAppearanceProperty(QString::fromLatin1("EmissiveColor"))) {
material.emissiveColor =
getAppearanceProperty(QString::fromLatin1("EmissiveColor"))->getColor();
custom = true;
}
if (hasAppearanceProperty(QString::fromLatin1("Shininess"))) {
material.shininess = getAppearanceProperty(QString::fromLatin1("Shininess"))->getFloat();
custom = true;
}
if (hasAppearanceProperty(QString::fromLatin1("Transparency"))) {
material.transparency =
getAppearanceProperty(QString::fromLatin1("Transparency"))->getFloat();
custom = true;
}
if (custom) {
material.setType(App::Material::USER_DEFINED);
material.uuid = getUUID().toStdString();
}
return material;
}

View File

@@ -31,6 +31,8 @@
#include <QTextStream>
#include <App/Application.h>
#include <App/Color.h>
#include <App/Material.h>
#include <Base/BaseClass.h>
#include <Mod/Material/MaterialGlobal.h>
@@ -79,11 +81,24 @@ public:
QString getString() const;
QString getYAMLString() const;
QString getDictionaryString() const; // Non-localized string
bool getBoolean() const;
int getInt() const;
double getFloat() const;
bool getBoolean() const
{
return getValue().toBool();
}
int getInt() const
{
return getValue().toInt();
}
double getFloat() const
{
return getValue().toFloat();
}
const Base::Quantity& getQuantity() const;
const QString getURL() const;
QString getURL() const
{
return getValue().toString();
}
App::Color getColor() const;
MaterialProperty& getColumn(int column);
const MaterialProperty& getColumn(int column) const;
@@ -109,6 +124,7 @@ public:
void setQuantity(const QString& value);
void setList(const QList<QVariant>& value);
void setURL(const QString& value);
void setColor(const App::Color& value);
MaterialProperty& operator=(const MaterialProperty& other);
friend QTextStream& operator<<(QTextStream& output, const MaterialProperty& property);
@@ -218,6 +234,8 @@ public:
return &_appearanceUuids;
}
App::Material getMaterialAppearance() const;
void setLibrary(const std::shared_ptr<MaterialLibrary>& library)
{
_library = library;
@@ -365,8 +383,24 @@ public:
void save(QTextStream& stream, bool overwrite, bool saveAsCopy, bool saveInherited);
/*
* Assignment operator
*/
Material& operator=(const Material& other);
/*
* Set the appearance properties
*/
Material& operator=(const App::Material& other);
bool operator==(const Material& other) const
{
if (&other == this) {
return true;
}
return getTypeId() == other.getTypeId() && _uuid == other._uuid;
}
protected:
void addModel(const QString& uuid);
static void removeUUID(QSet<QString>& uuidList, const QString& uuid);

View File

@@ -15,11 +15,6 @@
<Author Licence="LGPL" Name="DavidCarter" EMail="dcarter@davidcarter.ca" />
<UserDocu>Material property descriptions.</UserDocu>
</Documentation>
<!-- <Methode Name="mirror">
<Documentation>
<UserDocu>Performs the symmetrical transformation of this geometric object</UserDocu>
</Documentation>
</Methode> -->
<Attribute Name="Name" ReadOnly="true">
<Documentation>
<UserDocu>Property name.</UserDocu>

View File

@@ -0,0 +1,119 @@
/***************************************************************************
* Copyright (c) 2023 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_
#include <QMetaType>
#include <QUuid>
#endif
#include <App/Application.h>
#include <Base/Writer.h>
#include <Gui/MetaTypes.h>
#include "MaterialPy.h"
#include "PropertyMaterial.h"
using namespace Materials;
/* TRANSLATOR Material::PropertyMaterial */
TYPESYSTEM_SOURCE(Materials::PropertyMaterial, App::Property)
PropertyMaterial::PropertyMaterial() = default;
PropertyMaterial::~PropertyMaterial() = default;
void PropertyMaterial::setValue(const Material& mat)
{
aboutToSetValue();
_material = mat;
hasSetValue();
}
void PropertyMaterial::setValue(const App::Material& mat)
{
aboutToSetValue();
_material = mat;
hasSetValue();
}
const Material& PropertyMaterial::getValue() const
{
return _material;
}
PyObject* PropertyMaterial::getPyObject()
{
return new MaterialPy(new Material(_material));
}
void PropertyMaterial::setPyObject(PyObject* value)
{
if (PyObject_TypeCheck(value, &(MaterialPy::Type))) {
setValue(*static_cast<MaterialPy*>(value)->getMaterialPtr());
}
else {
std::string error = std::string("type must be 'Material' not ");
error += value->ob_type->tp_name;
throw Base::TypeError(error);
}
}
void PropertyMaterial::Save(Base::Writer& writer) const
{
writer.Stream() << writer.ind() << "<PropertyMaterial uuid=\""
<< _material.getUUID().toStdString() << "\"/>" << std::endl;
}
void PropertyMaterial::Restore(Base::XMLReader& reader)
{
// read my Element
reader.readElement("PropertyMaterial");
// get the value of my Attribute
aboutToSetValue();
auto uuid = reader.getAttribute("uuid");
_material.setUUID(QString::fromLatin1(uuid));
hasSetValue();
}
const char* PropertyMaterial::getEditorName() const
{
if (testStatus(MaterialEdit)) {
return ""; //"Gui::PropertyEditor::PropertyMaterialItem";
}
return "";
}
App::Property* PropertyMaterial::Copy() const
{
PropertyMaterial* p = new PropertyMaterial();
p->_material = _material;
return p;
}
void PropertyMaterial::Paste(const App::Property& from)
{
aboutToSetValue();
_material = dynamic_cast<const PropertyMaterial&>(from)._material;
hasSetValue();
}

View File

@@ -0,0 +1,101 @@
/***************************************************************************
* Copyright (c) 2023-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_PROPERTYMATERIAL_H
#define MATERIAL_PROPERTYMATERIAL_H
#include <App/Property.h>
#include <Base/Reader.h>
#include "Materials.h"
namespace App
{
class Material;
}
namespace Materials
{
/** Material properties
* This is the father of all properties handling colors.
*/
class MaterialsExport PropertyMaterial: public App::Property
{
TYPESYSTEM_HEADER_WITH_OVERRIDE();
public:
/**
* A constructor.
* A more elaborate description of the constructor.
*/
PropertyMaterial();
/**
* A destructor.
* A more elaborate description of the destructor.
*/
~PropertyMaterial() override;
/** Sets the property
*/
void setValue(const Material& mat);
/** Sets the appearance properties
*/
void setValue(const App::Material& mat);
/** This method returns a string representation of the property
*/
const Material& getValue() const;
PyObject* getPyObject() override;
void setPyObject(PyObject*) override;
void Save(Base::Writer& writer) const override;
void Restore(Base::XMLReader& reader) override;
const char* getEditorName() const override;
Property* Copy() const override;
void Paste(const Property& from) override;
unsigned int getMemSize() const override
{
return sizeof(_material);
}
bool isSame(const Property& other) const override
{
if (&other == this) {
return true;
}
return getTypeId() == other.getTypeId()
&& getValue() == static_cast<decltype(this)>(&other)->getValue();
}
private:
Material _material;
};
} // namespace Materials
#endif // MATERIAL_PROPERTYMATERIAL_H