Files
create/src/Mod/Part/Gui/ViewProviderPreviewExtension.cpp
2025-11-11 13:49:01 +01:00

273 lines
9.0 KiB
C++

// SPDX-License-Identifier: LGPL-2.1-or-later
/****************************************************************************
* *
* Copyright (c) 2025 Kacper Donat <kacper@kadet.net> *
* *
* 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 <Inventor/nodes/SoDrawStyle.h>
#include <Inventor/nodes/SoLightModel.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoPickStyle.h>
#include <Inventor/nodes/SoPolygonOffset.h>
#include <Inventor/nodes/SoTransform.h>
#include "ViewProviderPreviewExtension.h"
#include "ViewProviderExt.h"
#include <App/Document.h>
#include <Gui/Utilities.h>
#include <Gui/Inventor/So3DAnnotation.h>
#include <Mod/Part/App/PreviewExtension.h>
#include <Mod/Part/App/Tools.h>
using namespace PartGui;
SO_NODE_SOURCE(SoPreviewShape);
const SbColor SoPreviewShape::defaultColor = SbColor(1.F, 0.F, 1.F);
SoPreviewShape::SoPreviewShape()
{
SO_NODE_CONSTRUCTOR(SoPreviewShape);
SO_NODE_ADD_FIELD(color, (defaultColor));
SO_NODE_ADD_FIELD(transparency, (defaultTransparency));
SO_NODE_ADD_FIELD(lineWidth, (defaultLineWidth));
SO_NODE_ADD_FIELD(transform, (SbMatrix::identity()));
pcTransform = new SoMatrixTransform;
pcTransform->matrix.connectFrom(&transform);
auto pickStyle = new SoPickStyle;
pickStyle->style = SoPickStyle::UNPICKABLE;
auto* solidLineStyle = new SoDrawStyle();
solidLineStyle->lineWidth.connectFrom(&lineWidth);
auto* hiddenLineStyle = new SoDrawStyle();
hiddenLineStyle->lineWidth.connectFrom(&lineWidth);
hiddenLineStyle->linePattern = 0xF0F0;
auto* solidColorLightModel = new SoLightModel();
solidColorLightModel->model = SoLightModel::BASE_COLOR;
auto* normalBinding = new SoNormalBinding();
normalBinding->value = SoNormalBinding::PER_VERTEX_INDEXED;
// This should be OVERALL but then line pattern does not work correctly
// Probably a bug in coin to be investigated.
auto* materialBinding = new SoMaterialBinding();
materialBinding->value = SoMaterialBinding::PER_FACE_INDEXED;
auto* material = new SoMaterial;
material->diffuseColor.connectFrom(&color);
material->transparency.connectFrom(&transparency);
auto* polygonOffset = new SoPolygonOffset;
polygonOffset->factor = -0.00001F;
polygonOffset->units = -1.0F;
polygonOffset->on = true;
polygonOffset->styles = SoPolygonOffset::FILLED;
auto* lineMaterial = new SoMaterial;
lineMaterial->diffuseColor.connectFrom(&color);
lineMaterial->transparency = 0.0f;
auto* lineSep = new SoSeparator;
lineSep->addChild(normalBinding);
lineSep->addChild(materialBinding);
lineSep->addChild(solidColorLightModel);
lineSep->addChild(lineMaterial);
lineSep->addChild(lineset);
auto* annotation = new Gui::So3DAnnotation;
annotation->addChild(hiddenLineStyle);
annotation->addChild(material);
annotation->addChild(lineSep);
annotation->addChild(polygonOffset);
annotation->addChild(faceset);
SoSeparator::addChild(pcTransform);
SoSeparator::addChild(pickStyle);
SoSeparator::addChild(solidLineStyle);
SoSeparator::addChild(material);
SoSeparator::addChild(coords);
SoSeparator::addChild(norm);
SoSeparator::addChild(lineSep);
SoSeparator::addChild(polygonOffset);
SoSeparator::addChild(faceset);
SoSeparator::addChild(annotation);
}
void SoPreviewShape::initClass()
{
SO_NODE_INIT_CLASS(SoPreviewShape, SoSeparator, "Separator");
}
EXTENSION_PROPERTY_SOURCE(PartGui::ViewProviderPreviewExtension, Gui::ViewProviderExtension)
ViewProviderPreviewExtension::ViewProviderPreviewExtension()
{
const Base::Color magenta(1.0F, 0.0F, 1.0F);
EXTENSION_ADD_PROPERTY_TYPE(
PreviewColor,
(magenta),
"Preview",
static_cast<App::PropertyType>(App::Prop_Transient | App::Prop_Hidden),
"Color used for 3D Preview"
);
initExtensionType(ViewProviderPreviewExtension::getExtensionClassTypeId());
}
void ViewProviderPreviewExtension::extensionAttach(App::DocumentObject* documentObject)
{
ViewProviderExtension::extensionAttach(documentObject);
pcPreviewRoot = new SoSeparator;
pcPreviewShape = new SoPreviewShape;
attachPreview();
auto document = documentObject->getDocument();
if (!document->testStatus(App::Document::Restoring)) {
updatePreview();
}
}
void ViewProviderPreviewExtension::extensionBeforeDelete()
{
ViewProviderExtension::extensionBeforeDelete();
showPreview(false);
}
void ViewProviderPreviewExtension::showPreview(bool enable)
{
auto feature = getExtendedViewProvider()->getObject<Part::Feature>();
if (!feature) {
return;
}
auto previewExtension = feature->getExtensionByType<Part::PreviewExtension>(true);
if (!previewExtension) {
return;
}
_isPreviewEnabled = enable;
auto annotationRoot = getExtendedViewProvider()->getAnnotation();
if (enable) {
previewExtension->updatePreview();
if (annotationRoot->findChild(pcPreviewRoot) < 0) {
annotationRoot->addChild(pcPreviewRoot);
}
}
else {
annotationRoot->removeChild(pcPreviewRoot);
}
}
void ViewProviderPreviewExtension::extensionOnChanged(const App::Property* prop)
{
if (prop == &PreviewColor) {
pcPreviewShape->color.setValue(Base::convertTo<SbColor>(PreviewColor.getValue()));
}
ViewProviderExtension::extensionOnChanged(prop);
}
void ViewProviderPreviewExtension::attachPreview()
{
pcPreviewRoot->addChild(pcPreviewShape);
}
void ViewProviderPreviewExtension::updatePreview()
{
updatePreviewShape(getPreviewShape(), pcPreviewShape);
}
void ViewProviderPreviewExtension::updatePreviewShape(Part::TopoShape shape, SoPreviewShape* preview)
{
if (shape.isNull() || preview == nullptr) {
return;
}
auto vp = freecad_cast<ViewProviderPartExt*>(getExtendedViewProvider());
if (!vp) {
return;
}
const auto updatePreviewShape = [vp](SoPreviewShape* preview, Part::TopoShape shape) {
ViewProviderPartExt::setupCoinGeometry(
shape.getShape(),
preview,
vp->Deviation.getValue(),
vp->AngularDeflection.getValue()
);
};
try {
updatePreviewShape(preview, shape);
preview->transform.setValue(Base::convertTo<SbMatrix>(shape.getTransform()));
}
catch (Standard_Failure& e) {
Base::Console().userTranslatedNotification(
tr("Failure while rendering preview: %1. That usually indicates an error with model.")
.arg(QString::fromUtf8(e.GetMessageString()))
.toUtf8()
);
updatePreviewShape(preview, {});
}
// For some reason line patterns are not rendered correctly if material binding is set to
// anything other than PER_FACE. PER_FACE material binding seems to require materialIndex per
// each distinct edge. Until that is fixed, this code forces each edge to use the first
// material.
unsigned lineCoordsCount = preview->lineset->coordIndex.getNum();
unsigned lineCount = 1;
for (unsigned i = 0; i < lineCoordsCount; ++i) {
if (preview->lineset->coordIndex[i] < 0) {
lineCount++;
}
}
preview->lineset->materialIndex.setNum(lineCount);
for (unsigned i = 0; i < lineCount; ++i) {
preview->lineset->materialIndex.set1Value(i, 0);
}
}
namespace Gui
{
EXTENSION_PROPERTY_SOURCE_TEMPLATE(
PartGui::ViewProviderPreviewExtensionPython,
PartGui::ViewProviderPreviewExtension
)
// explicit template instantiation
template class PartGuiExport ViewProviderExtensionPythonT<PartGui::ViewProviderPreviewExtension>;
} // namespace Gui