PartDesign: Add transparent previews

This commit is contained in:
Kacper Donat
2024-10-13 13:33:18 +02:00
parent 38db306a84
commit 7f87d87f61
58 changed files with 1438 additions and 1021 deletions

View File

@@ -98,6 +98,7 @@
#include "GeometryIntExtensionPy.h"
#include "GeometryMigrationExtension.h"
#include "GeometryStringExtensionPy.h"
#include "PreviewExtension.h"
#include "HyperbolaPy.h"
#include "ImportStep.h"
#include "LinePy.h"
@@ -435,7 +436,7 @@ PyMOD_INIT_FUNC(Part)
Part::AttachExtension ::init();
Part::AttachExtensionPython ::init();
Part::PreviewExtension ::init();
Part::PrismExtension ::init();
Part::Feature ::init();

View File

@@ -225,6 +225,8 @@ SET(Features_SRCS
DatumFeature.h
AttachExtension.h
AttachExtension.cpp
PreviewExtension.cpp
PreviewExtension.h
PrismExtension.cpp
PrismExtension.h
VectorAdapter.cpp

View File

@@ -0,0 +1,67 @@
// 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 "PreCompiled.h"
#include "PreviewExtension.h"
#include <App/DocumentObject.h>
EXTENSION_PROPERTY_SOURCE(Part::PreviewExtension, App::DocumentObjectExtension)
EXTENSION_PROPERTY_SOURCE_TEMPLATE(Part::PreviewExtensionPython, Part::PreviewExtension)
Part::PreviewExtension::PreviewExtension()
{
initExtensionType(getExtensionClassTypeId());
EXTENSION_ADD_PROPERTY(PreviewShape, (TopoShape()));
PreviewShape.setStatus(App::Property::Output, true);
PreviewShape.setStatus(App::Property::Transient, true);
PreviewShape.setStatus(App::Property::Hidden, true);
}
void Part::PreviewExtension::updatePreview()
{
if (_isPreviewFresh) {
return;
}
recomputePreview();
_isPreviewFresh = true;
}
bool Part::PreviewExtension::mustRecomputePreview() const
{
return getExtendedObject()->mustRecompute();
}
void Part::PreviewExtension::extensionOnChanged(const App::Property* prop)
{
DocumentObjectExtension::extensionOnChanged(prop);
if (mustRecomputePreview()) {
_isPreviewFresh = false;
}
}

View File

@@ -0,0 +1,82 @@
// 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/>. *
* *
***************************************************************************/
#ifndef PART_PREVIEWEXTENSION_H
#define PART_PREVIEWEXTENSION_H
#include "PropertyTopoShape.h"
#include <App/DocumentObject.h>
#include <App/DocumentObjectExtension.h>
#include <App/ExtensionPython.h>
namespace Part
{
class PartExport PreviewExtension: public App::DocumentObjectExtension
{
EXTENSION_PROPERTY_HEADER_WITH_OVERRIDE(Part::PreviewExtension);
using inherited = DocumentObjectExtension;
public:
/// Shape displayed as a semi-transparent preview, should be a delta between current state and
/// previous one
PropertyPartShape PreviewShape;
PreviewExtension();
bool isPreviewFresh() const
{
return _isPreviewFresh;
}
void updatePreview();
virtual bool mustRecomputePreview() const;
protected:
void extensionOnChanged(const App::Property* prop) override;
virtual App::DocumentObjectExecReturn* recomputePreview()
{
return App::DocumentObject::StdReturn;
};
private:
bool _isPreviewFresh {false};
};
template<typename ExtensionT>
class PreviewExtensionPythonT: public ExtensionT
{
public:
PreviewExtensionPythonT() = default;
~PreviewExtensionPythonT() override = default;
};
using PreviewExtensionPython = App::ExtensionPythonT<PreviewExtensionPythonT<PreviewExtension>>;
} // namespace Part
#endif // PART_PREVIEWEXTENSION_H

View File

@@ -71,7 +71,11 @@
#include "Tools.h"
#include <TopExp_Explorer.hxx>
#include <TopoShape.h>
class TopExp_Explorer;
void Part::closestPointsOnLines(const gp_Lin& lin1, const gp_Lin& lin2, gp_Pnt& p1, gp_Pnt& p2)
{
// they might be the same point
@@ -746,36 +750,71 @@ TopLoc_Location Part::Tools::fromPlacement(const Base::Placement& plm)
return {trf};
}
bool Part::Tools::isConcave(const TopoDS_Face &face, const gp_Pnt &pointOfVue, const gp_Dir &direction){
bool Part::Tools::isConcave(const TopoDS_Face& face,
const gp_Pnt& pointOfVue,
const gp_Dir& direction)
{
bool result = false;
Handle(Geom_Surface) surf = BRep_Tool::Surface(face);
GeomAdaptor_Surface adapt(surf);
if(adapt.GetType() == GeomAbs_Plane){
if (adapt.GetType() == GeomAbs_Plane) {
return false;
}
// create a line through the point of vue
// create a line through the point of vue
gp_Lin line;
line.SetLocation(pointOfVue);
line.SetDirection(direction);
// Find intersection of line with the face
// Find intersection of line with the face
BRepIntCurveSurface_Inter mkSection;
mkSection.Init(face, line, Precision::Confusion());
result = mkSection.Transition() == IntCurveSurface_In;
// compute normals at the intersection
// compute normals at the intersection
gp_Pnt iPnt;
gp_Vec dU, dV;
surf->D1(mkSection.U(), mkSection.V(), iPnt, dU, dV);
// check normals orientation
// check normals orientation
gp_Dir dirdU(dU);
result = (dirdU.Angle(direction) - std::numbers::pi/2) <= Precision::Confusion();
result = (dirdU.Angle(direction) - std::numbers::pi / 2) <= Precision::Confusion();
gp_Dir dirdV(dV);
result = result || ((dirdV.Angle(direction) - std::numbers::pi/2) <= Precision::Confusion());
result = result || ((dirdV.Angle(direction) - std::numbers::pi / 2) <= Precision::Confusion());
return result;
}
bool Part::Tools::isShapeEmpty(const TopoShape& shape)
{
return shape.isEmpty();
}
bool Part::Tools::isShapeEmpty(const TopoDS_Shape& shape)
{
static const auto isEveryShapeInCompoundEmpty = [](const TopoDS_Shape& shape) {
for (TopoDS_Iterator it(shape); it.More(); it.Next()) {
if (const TopoDS_Shape& sub = it.Value(); !isShapeEmpty(sub)) {
// Found a non-empty sub-shape
return false;
}
}
return true;
};
// If shape is null we consider it as empty
if (shape.IsNull()) {
return true;
}
if (shape.ShapeType() == TopAbs_COMPOUND) {
return isEveryShapeInCompoundEmpty(shape);
}
// To see if shape is non-empty we check if it has at least one vertex
TopExp_Explorer explorer(shape, TopAbs_VERTEX);
return !explorer.More();
}

View File

@@ -43,6 +43,10 @@
#include <vector>
namespace Part
{
class TopoShape;
}
class gp_Lin;
class gp_Pln;
@@ -235,6 +239,26 @@ public:
* and false otherwise, plane case included.
*/
static bool isConcave(const TopoDS_Face &face, const gp_Pnt &pointOfVue, const gp_Dir &direction);
/**
* \copydoc Part::Tools::isShapeEmpty(const TopoDS_Shape&)
*/
static bool isShapeEmpty(const TopoShape& shape);
/**
* \brief Determines whether the given \ref TopoDS_Shape is empty.
*
* This function evaluates whether a given shape is considered "empty."
*
* A shape is empty if:
* - It is null (uninitialized).
* - It is a compound shape (i.e., a container for sub-shapes), but all its sub-shapes are empty.
* - It does not have any geometry.
*
* \param[in] shape The shape to evaluate.
* \return `true` if the shape is empty, otherwise `false`.
*/
static bool isShapeEmpty(const TopoDS_Shape& shape);
};
} //namespace Part

View File

@@ -1361,6 +1361,11 @@ bool TopoShape::isValid() const
return aChecker.IsValid() ? true : false;
}
bool TopoShape::isEmpty() const
{
return Tools::isShapeEmpty(this->_Shape);
}
namespace Part {
std::vector<std::string> buildShapeEnumVector()
{

View File

@@ -487,6 +487,7 @@ public:
//@{
bool isNull() const;
bool isValid() const;
bool isEmpty() const;
bool analyze(bool runBopCheck, std::ostream&) const;
bool isClosed() const;
bool isCoplanar(const TopoShape& other, double tol = -1) const;

View File

@@ -68,6 +68,7 @@
#include "ViewProviderMirror.h"
#include "ViewProviderPlaneParametric.h"
#include "ViewProviderPointParametric.h"
#include "ViewProviderPreviewExtension.h"
#include "ViewProviderPrism.h"
#include "ViewProviderProjectOnSurface.h"
#include "ViewProviderRegularPolygon.h"
@@ -162,10 +163,13 @@ PyMOD_INIT_FUNC(PartGui)
PartGui::SoBrepEdgeSet ::initClass();
PartGui::SoBrepPointSet ::initClass();
PartGui::SoFCControlPoints ::initClass();
PartGui::SoPreviewShape ::initClass();
PartGui::ViewProviderAttachExtension ::init();
PartGui::ViewProviderAttachExtensionPython ::init();
PartGui::ViewProviderGridExtension ::init();
PartGui::ViewProviderGridExtensionPython ::init();
PartGui::ViewProviderPreviewExtension ::init();
PartGui::ViewProviderPreviewExtensionPython ::init();
PartGui::ViewProviderSplineExtension ::init();
PartGui::ViewProviderSplineExtensionPython ::init();
PartGui::ViewProviderLine ::init();

View File

@@ -162,6 +162,8 @@ SET(PartGui_SRCS
ViewProvider.h
ViewProviderAttachExtension.h
ViewProviderAttachExtension.cpp
ViewProviderPreviewExtension.h
ViewProviderPreviewExtension.cpp
ViewProviderDatum.cpp
ViewProviderDatum.h
ViewProviderExt.cpp

View File

@@ -564,7 +564,7 @@ std::vector<Base::Vector3d> ViewProviderPartExt::getModelPoints(const SoPickedPo
try {
std::vector<Base::Vector3d> pts;
std::string element = this->getElement(pp->getDetail());
const auto &shape = Part::Feature::getTopoShape(getObject(), Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform);
const auto &shape = getRenderedShape();
TopoDS_Shape subShape = shape.getSubShape(element.c_str());
@@ -926,6 +926,404 @@ void ViewProviderPartExt::unsetEdit(int ModNum)
}
}
void ViewProviderPartExt::setupCoinGeometry(TopoDS_Shape shape,
SoCoordinate3* coords,
SoBrepFaceSet* faceset,
SoNormal* norm,
SoBrepEdgeSet* lineset,
SoBrepPointSet* nodeset,
double deviation,
double angularDeflection,
bool normalsFromUV)
{
if (Part::Tools::isShapeEmpty(shape)) {
coords->point.setNum(0);
norm->vector.setNum(0);
faceset->coordIndex.setNum(0);
faceset->partIndex.setNum(0);
lineset->coordIndex.setNum(0);
nodeset->startIndex.setValue(0);
return;
}
// time measurement and book keeping
Base::TimeElapsed startTime;
[[maybe_unused]]
int numTriangles = 0, numNodes = 0, numNorms = 0, numFaces = 0, numEdges = 0, numLines = 0;
std::set<int> faceEdges;
// calculating the deflection value
Bnd_Box bounds;
BRepBndLib::Add(shape, bounds);
bounds.SetGap(0.0);
Standard_Real xMin, yMin, zMin, xMax, yMax, zMax;
bounds.Get(xMin, yMin, zMin, xMax, yMax, zMax);
Standard_Real deflection = ((xMax - xMin) + (yMax - yMin) + (zMax - zMin)) / 300.0 * deviation;
// Since OCCT 7.6 a value of equal 0 is not allowed any more, this can happen if a single
// vertex should be displayed.
if (deflection < gp::Resolution()) {
deflection = Precision::Confusion();
}
// For very big objects the computed deflection can become very high and thus leads to a
// useless tessellation. To avoid this the upper limit is set to 20.0 See also forum:
// https://forum.freecad.org/viewtopic.php?t=77521
// deflection = std::min(deflection, 20.0);
// create or use the mesh on the data structure
Standard_Real AngDeflectionRads = Base::toRadians(angularDeflection);
IMeshTools_Parameters meshParams;
meshParams.Deflection = deflection;
meshParams.Relative = Standard_False;
meshParams.Angle = AngDeflectionRads;
meshParams.InParallel = Standard_True;
meshParams.AllowQualityDecrease = Standard_True;
BRepMesh_IncrementalMesh(shape, meshParams);
// We must reset the location here because the transformation data
// are set in the placement property
TopLoc_Location aLoc;
shape.Location(aLoc);
// count triangles and nodes in the mesh
TopTools_IndexedMapOfShape faceMap;
TopExp::MapShapes(shape, TopAbs_FACE, faceMap);
for (int i = 1; i <= faceMap.Extent(); i++) {
Handle(Poly_Triangulation) mesh = BRep_Tool::Triangulation(TopoDS::Face(faceMap(i)), aLoc);
if (mesh.IsNull()) {
mesh = Part::Tools::triangulationOfFace(TopoDS::Face(faceMap(i)));
}
// Note: we must also count empty faces
if (!mesh.IsNull()) {
numTriangles += mesh->NbTriangles();
numNodes += mesh->NbNodes();
numNorms += mesh->NbNodes();
}
TopExp_Explorer xp;
for (xp.Init(faceMap(i), TopAbs_EDGE); xp.More(); xp.Next()) {
faceEdges.insert(Part::ShapeMapHasher {}(xp.Current()));
}
numFaces++;
}
// get an indexed map of edges
TopTools_IndexedMapOfShape edgeMap;
TopExp::MapShapes(shape, TopAbs_EDGE, edgeMap);
// key is the edge number, value the coord indexes. This is needed to keep the same order as
// the edges.
std::map<int, std::vector<int32_t>> lineSetMap;
std::set<int> edgeIdxSet;
std::vector<int32_t> edgeVector;
// count and index the edges
for (int i = 1; i <= edgeMap.Extent(); i++) {
edgeIdxSet.insert(i);
numEdges++;
const TopoDS_Edge& aEdge = TopoDS::Edge(edgeMap(i));
TopLoc_Location aLoc;
// handling of the free edge that are not associated to a face
// Note: The assumption that if for an edge BRep_Tool::Polygon3D
// returns a valid object is wrong. This e.g. happens for ruled
// surfaces which gets created by two edges or wires.
// So, we have to store the hashes of the edges associated to a face.
// If the hash of a given edge is not in this list we know it's really
// a free edge.
int hash = Part::ShapeMapHasher {}(aEdge);
if (faceEdges.find(hash) == faceEdges.end()) {
Handle(Poly_Polygon3D) aPoly = Part::Tools::polygonOfEdge(aEdge, aLoc);
if (!aPoly.IsNull()) {
int nbNodesInEdge = aPoly->NbNodes();
numNodes += nbNodesInEdge;
}
}
}
// handling of the vertices
TopTools_IndexedMapOfShape vertexMap;
TopExp::MapShapes(shape, TopAbs_VERTEX, vertexMap);
numNodes += vertexMap.Extent();
// create memory for the nodes and indexes
coords->point.setNum(numNodes);
norm->vector.setNum(numNorms);
faceset->coordIndex.setNum(numTriangles * 4);
faceset->partIndex.setNum(numFaces);
// get the raw memory for fast fill up
SbVec3f* verts = coords->point.startEditing();
SbVec3f* norms = norm->vector.startEditing();
int32_t* index = faceset->coordIndex.startEditing();
int32_t* parts = faceset->partIndex.startEditing();
// preset the normal vector with null vector
for (int i = 0; i < numNorms; i++) {
norms[i] = SbVec3f(0.0, 0.0, 0.0);
}
int ii = 0, faceNodeOffset = 0, faceTriaOffset = 0;
for (int i = 1; i <= faceMap.Extent(); i++, ii++) {
TopLoc_Location aLoc;
const TopoDS_Face& actFace = TopoDS::Face(faceMap(i));
// get the mesh of the shape
Handle(Poly_Triangulation) mesh = BRep_Tool::Triangulation(actFace, aLoc);
if (mesh.IsNull()) {
mesh = Part::Tools::triangulationOfFace(actFace);
}
if (mesh.IsNull()) {
parts[ii] = 0;
continue;
}
// getting the transformation of the shape/face
gp_Trsf myTransf;
Standard_Boolean identity = true;
if (!aLoc.IsIdentity()) {
identity = false;
myTransf = aLoc.Transformation();
}
// getting size of node and triangle array of this face
int nbNodesInFace = mesh->NbNodes();
int nbTriInFace = mesh->NbTriangles();
// check orientation
TopAbs_Orientation orient = actFace.Orientation();
// cycling through the poly mesh
#if OCC_VERSION_HEX < 0x070600
const Poly_Array1OfTriangle& Triangles = mesh->Triangles();
const TColgp_Array1OfPnt& Nodes = mesh->Nodes();
TColgp_Array1OfDir Normals(Nodes.Lower(), Nodes.Upper());
#else
int numNodes = mesh->NbNodes();
TColgp_Array1OfDir Normals(1, numNodes);
#endif
if (normalsFromUV) {
Part::Tools::getPointNormals(actFace, mesh, Normals);
}
for (int g = 1; g <= nbTriInFace; g++) {
// Get the triangle
Standard_Integer N1, N2, N3;
#if OCC_VERSION_HEX < 0x070600
Triangles(g).Get(N1, N2, N3);
#else
mesh->Triangle(g).Get(N1, N2, N3);
#endif
// change orientation of the triangle if the face is reversed
if (orient != TopAbs_FORWARD) {
Standard_Integer tmp = N1;
N1 = N2;
N2 = tmp;
}
// get the 3 points of this triangle
#if OCC_VERSION_HEX < 0x070600
gp_Pnt V1(Nodes(N1)), V2(Nodes(N2)), V3(Nodes(N3));
#else
gp_Pnt V1(mesh->Node(N1)), V2(mesh->Node(N2)), V3(mesh->Node(N3));
#endif
// get the 3 normals of this triangle
gp_Vec NV1, NV2, NV3;
if (normalsFromUV) {
NV1.SetXYZ(Normals(N1).XYZ());
NV2.SetXYZ(Normals(N2).XYZ());
NV3.SetXYZ(Normals(N3).XYZ());
}
else {
gp_Vec v1(V1.X(), V1.Y(), V1.Z()),
v2(V2.X(), V2.Y(), V2.Z()),
v3(V3.X(), V3.Y(), V3.Z());
gp_Vec normal = (v2 - v1) ^ (v3 - v1);
NV1 = normal;
NV2 = normal;
NV3 = normal;
}
// transform the vertices and normals to the place of the face
if (!identity) {
V1.Transform(myTransf);
V2.Transform(myTransf);
V3.Transform(myTransf);
if (normalsFromUV) {
NV1.Transform(myTransf);
NV2.Transform(myTransf);
NV3.Transform(myTransf);
}
}
// add the normals for all points of this triangle
norms[faceNodeOffset + N1 - 1] += SbVec3f(NV1.X(), NV1.Y(), NV1.Z());
norms[faceNodeOffset + N2 - 1] += SbVec3f(NV2.X(), NV2.Y(), NV2.Z());
norms[faceNodeOffset + N3 - 1] += SbVec3f(NV3.X(), NV3.Y(), NV3.Z());
// set the vertices
verts[faceNodeOffset + N1 - 1].setValue((float)(V1.X()),
(float)(V1.Y()),
(float)(V1.Z()));
verts[faceNodeOffset + N2 - 1].setValue((float)(V2.X()),
(float)(V2.Y()),
(float)(V2.Z()));
verts[faceNodeOffset + N3 - 1].setValue((float)(V3.X()),
(float)(V3.Y()),
(float)(V3.Z()));
// set the index vector with the 3 point indexes and the end delimiter
index[faceTriaOffset * 4 + 4 * (g - 1)] = faceNodeOffset + N1 - 1;
index[faceTriaOffset * 4 + 4 * (g - 1) + 1] = faceNodeOffset + N2 - 1;
index[faceTriaOffset * 4 + 4 * (g - 1) + 2] = faceNodeOffset + N3 - 1;
index[faceTriaOffset * 4 + 4 * (g - 1) + 3] = SO_END_FACE_INDEX;
}
parts[ii] = nbTriInFace; // new part
// handling the edges lying on this face
TopExp_Explorer Exp;
for (Exp.Init(actFace, TopAbs_EDGE); Exp.More(); Exp.Next()) {
const TopoDS_Edge& curEdge = TopoDS::Edge(Exp.Current());
// get the overall index of this edge
int edgeIndex = edgeMap.FindIndex(curEdge);
edgeVector.push_back((int32_t)edgeIndex - 1);
// already processed this index ?
if (edgeIdxSet.find(edgeIndex) != edgeIdxSet.end()) {
// this holds the indices of the edge's triangulation to the current polygon
Handle(Poly_PolygonOnTriangulation) aPoly =
BRep_Tool::PolygonOnTriangulation(curEdge, mesh, aLoc);
if (aPoly.IsNull()) {
continue; // polygon does not exist
}
// getting the indexes of the edge polygon
const TColStd_Array1OfInteger& indices = aPoly->Nodes();
for (Standard_Integer i = indices.Lower(); i <= indices.Upper(); i++) {
int nodeIndex = indices(i);
int index = faceNodeOffset + nodeIndex - 1;
lineSetMap[edgeIndex].push_back(index);
// usually the coordinates for this edge are already set by the
// triangles of the face this edge belongs to. However, there are
// rare cases where some points are only referenced by the polygon
// but not by any triangle. Thus, we must apply the coordinates to
// make sure that everything is properly set.
#if OCC_VERSION_HEX < 0x070600
gp_Pnt p(Nodes(nodeIndex));
#else
gp_Pnt p(mesh->Node(nodeIndex));
#endif
if (!identity) {
p.Transform(myTransf);
}
verts[index].setValue((float)(p.X()), (float)(p.Y()), (float)(p.Z()));
}
// remove the handled edge index from the set
edgeIdxSet.erase(edgeIndex);
}
}
edgeVector.push_back(-1);
// counting up the per Face offsets
faceNodeOffset += nbNodesInFace;
faceTriaOffset += nbTriInFace;
}
// handling of the free edges
for (int i = 1; i <= edgeMap.Extent(); i++) {
const TopoDS_Edge& aEdge = TopoDS::Edge(edgeMap(i));
Standard_Boolean identity = true;
gp_Trsf myTransf;
TopLoc_Location aLoc;
// handling of the free edge that are not associated to a face
int hash = Part::ShapeMapHasher {}(aEdge);
if (faceEdges.find(hash) == faceEdges.end()) {
Handle(Poly_Polygon3D) aPoly = Part::Tools::polygonOfEdge(aEdge, aLoc);
if (!aPoly.IsNull()) {
if (!aLoc.IsIdentity()) {
identity = false;
myTransf = aLoc.Transformation();
}
const TColgp_Array1OfPnt& aNodes = aPoly->Nodes();
int nbNodesInEdge = aPoly->NbNodes();
gp_Pnt pnt;
for (Standard_Integer j = 1; j <= nbNodesInEdge; j++) {
pnt = aNodes(j);
if (!identity) {
pnt.Transform(myTransf);
}
int index = faceNodeOffset + j - 1;
verts[index].setValue((float)(pnt.X()), (float)(pnt.Y()), (float)(pnt.Z()));
lineSetMap[i].push_back(index);
}
faceNodeOffset += nbNodesInEdge;
}
}
}
nodeset->startIndex.setValue(faceNodeOffset);
for (int i = 0; i < vertexMap.Extent(); i++) {
const TopoDS_Vertex& aVertex = TopoDS::Vertex(vertexMap(i + 1));
gp_Pnt pnt = BRep_Tool::Pnt(aVertex);
verts[faceNodeOffset + i].setValue((float)(pnt.X()),
(float)(pnt.Y()),
(float)(pnt.Z()));
}
// normalize all normals
for (int i = 0; i < numNorms; i++) {
norms[i].normalize();
}
std::vector<int32_t> lineSetCoords;
for (const auto& it : lineSetMap) {
lineSetCoords.insert(lineSetCoords.end(), it.second.begin(), it.second.end());
lineSetCoords.push_back(-1);
}
// preset the index vector size
numLines = lineSetCoords.size();
lineset->coordIndex.setNum(numLines);
int32_t* lines = lineset->coordIndex.startEditing();
int l = 0;
for (std::vector<int32_t>::const_iterator it = lineSetCoords.begin();
it != lineSetCoords.end();
++it, l++) {
lines[l] = *it;
}
// end the editing of the nodes
coords->point.finishEditing();
norm->vector.finishEditing();
faceset->coordIndex.finishEditing();
faceset->partIndex.finishEditing();
lineset->coordIndex.finishEditing();
# ifdef FC_DEBUG
// printing some information
Base::Console().log("ViewProvider update time: %f s\n",Base::TimeElapsed::diffTimeF(startTime,Base::TimeElapsed()));
Base::Console().log("Shape tria info: Faces:%d Edges:%d Nodes:%d Triangles:%d IdxVec:%d\n",numFaces,numEdges,numNodes,numTriangles,numLines);
# endif
}
void ViewProviderPartExt::updateVisual()
{
Gui::SoUpdateVBOAction action;
@@ -943,382 +1341,30 @@ void ViewProviderPartExt::updateVisual()
haction.apply(this->lineset);
haction.apply(this->nodeset);
TopoDS_Shape cShape = Part::Feature::getShape(getObject(), Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform);
if (cShape.IsNull()) {
coords ->point .setNum(0);
norm ->vector .setNum(0);
faceset ->coordIndex .setNum(0);
faceset ->partIndex .setNum(0);
lineset ->coordIndex .setNum(0);
nodeset ->startIndex .setValue(0);
VisualTouched = false;
return;
}
// time measurement and book keeping
Base::TimeElapsed start_time;
int numTriangles=0,numNodes=0,numNorms=0,numFaces=0,numEdges=0,numLines=0;
std::set<int> faceEdges;
try {
// calculating the deflection value
Bnd_Box bounds;
BRepBndLib::Add(cShape, bounds);
bounds.SetGap(0.0);
Standard_Real xMin, yMin, zMin, xMax, yMax, zMax;
bounds.Get(xMin, yMin, zMin, xMax, yMax, zMax);
Standard_Real deflection = ((xMax-xMin)+(yMax-yMin)+(zMax-zMin))/300.0 * Deviation.getValue();
TopoDS_Shape cShape = getRenderedShape().getShape();
// Since OCCT 7.6 a value of equal 0 is not allowed any more, this can happen if a single vertex
// should be displayed.
if (deflection < gp::Resolution()) {
deflection = Precision::Confusion();
}
setupCoinGeometry(cShape,
coords,
faceset,
norm,
lineset,
nodeset,
Deviation.getValue(),
AngularDeflection.getValue(),
NormalsFromUV);
// For very big objects the computed deflection can become very high and thus leads to a useless
// tessellation. To avoid this the upper limit is set to 20.0
// See also forum: https://forum.freecad.org/viewtopic.php?t=77521
//deflection = std::min(deflection, 20.0);
// create or use the mesh on the data structure
Standard_Real AngDeflectionRads = Base::toRadians(AngularDeflection.getValue());
IMeshTools_Parameters meshParams;
meshParams.Deflection = deflection;
meshParams.Relative = Standard_False;
meshParams.Angle = AngDeflectionRads;
meshParams.InParallel = Standard_True;
meshParams.AllowQualityDecrease = Standard_True;
BRepMesh_IncrementalMesh(cShape, meshParams);
// We must reset the location here because the transformation data
// are set in the placement property
TopLoc_Location aLoc;
cShape.Location(aLoc);
// count triangles and nodes in the mesh
TopTools_IndexedMapOfShape faceMap;
TopExp::MapShapes(cShape, TopAbs_FACE, faceMap);
for (int i=1; i <= faceMap.Extent(); i++) {
Handle (Poly_Triangulation) mesh = BRep_Tool::Triangulation(TopoDS::Face(faceMap(i)), aLoc);
if (mesh.IsNull()) {
mesh = Part::Tools::triangulationOfFace(TopoDS::Face(faceMap(i)));
}
// Note: we must also count empty faces
if (!mesh.IsNull()) {
numTriangles += mesh->NbTriangles();
numNodes += mesh->NbNodes();
numNorms += mesh->NbNodes();
}
TopExp_Explorer xp;
for (xp.Init(faceMap(i),TopAbs_EDGE);xp.More();xp.Next()) {
faceEdges.insert(Part::ShapeMapHasher{}(xp.Current()));
}
numFaces++;
}
// get an indexed map of edges
TopTools_IndexedMapOfShape edgeMap;
TopExp::MapShapes(cShape, TopAbs_EDGE, edgeMap);
// key is the edge number, value the coord indexes. This is needed to keep the same order as the edges.
std::map<int, std::vector<int32_t> > lineSetMap;
std::set<int> edgeIdxSet;
std::vector<int32_t> edgeVector;
// count and index the edges
for (int i=1; i <= edgeMap.Extent(); i++) {
edgeIdxSet.insert(i);
numEdges++;
const TopoDS_Edge& aEdge = TopoDS::Edge(edgeMap(i));
TopLoc_Location aLoc;
// handling of the free edge that are not associated to a face
// Note: The assumption that if for an edge BRep_Tool::Polygon3D
// returns a valid object is wrong. This e.g. happens for ruled
// surfaces which gets created by two edges or wires.
// So, we have to store the hashes of the edges associated to a face.
// If the hash of a given edge is not in this list we know it's really
// a free edge.
int hash = Part::ShapeMapHasher{}(aEdge);
if (faceEdges.find(hash) == faceEdges.end()) {
Handle(Poly_Polygon3D) aPoly = Part::Tools::polygonOfEdge(aEdge, aLoc);
if (!aPoly.IsNull()) {
int nbNodesInEdge = aPoly->NbNodes();
numNodes += nbNodesInEdge;
}
}
}
// handling of the vertices
TopTools_IndexedMapOfShape vertexMap;
TopExp::MapShapes(cShape, TopAbs_VERTEX, vertexMap);
numNodes += vertexMap.Extent();
// create memory for the nodes and indexes
coords ->point .setNum(numNodes);
norm ->vector .setNum(numNorms);
faceset ->coordIndex .setNum(numTriangles*4);
faceset ->partIndex .setNum(numFaces);
// get the raw memory for fast fill up
SbVec3f* verts = coords ->point .startEditing();
SbVec3f* norms = norm ->vector .startEditing();
int32_t* index = faceset ->coordIndex .startEditing();
int32_t* parts = faceset ->partIndex .startEditing();
// preset the normal vector with null vector
for (int i=0;i < numNorms;i++)
norms[i]= SbVec3f(0.0,0.0,0.0);
int ii = 0,faceNodeOffset=0,faceTriaOffset=0;
for (int i=1; i <= faceMap.Extent(); i++, ii++) {
TopLoc_Location aLoc;
const TopoDS_Face &actFace = TopoDS::Face(faceMap(i));
// get the mesh of the shape
Handle (Poly_Triangulation) mesh = BRep_Tool::Triangulation(actFace,aLoc);
if (mesh.IsNull()) {
mesh = Part::Tools::triangulationOfFace(actFace);
}
if (mesh.IsNull()) {
parts[ii] = 0;
continue;
}
// getting the transformation of the shape/face
gp_Trsf myTransf;
Standard_Boolean identity = true;
if (!aLoc.IsIdentity()) {
identity = false;
myTransf = aLoc.Transformation();
}
// getting size of node and triangle array of this face
int nbNodesInFace = mesh->NbNodes();
int nbTriInFace = mesh->NbTriangles();
// check orientation
TopAbs_Orientation orient = actFace.Orientation();
// cycling through the poly mesh
#if OCC_VERSION_HEX < 0x070600
const Poly_Array1OfTriangle& Triangles = mesh->Triangles();
const TColgp_Array1OfPnt& Nodes = mesh->Nodes();
TColgp_Array1OfDir Normals (Nodes.Lower(), Nodes.Upper());
#else
int numNodes = mesh->NbNodes();
TColgp_Array1OfDir Normals (1, numNodes);
#endif
if (NormalsFromUV)
Part::Tools::getPointNormals(actFace, mesh, Normals);
for (int g=1;g<=nbTriInFace;g++) {
// Get the triangle
Standard_Integer N1,N2,N3;
#if OCC_VERSION_HEX < 0x070600
Triangles(g).Get(N1,N2,N3);
#else
mesh->Triangle(g).Get(N1,N2,N3);
#endif
// change orientation of the triangle if the face is reversed
if ( orient != TopAbs_FORWARD ) {
Standard_Integer tmp = N1;
N1 = N2;
N2 = tmp;
}
// get the 3 points of this triangle
#if OCC_VERSION_HEX < 0x070600
gp_Pnt V1(Nodes(N1)), V2(Nodes(N2)), V3(Nodes(N3));
#else
gp_Pnt V1(mesh->Node(N1)), V2(mesh->Node(N2)), V3(mesh->Node(N3));
#endif
// get the 3 normals of this triangle
gp_Vec NV1, NV2, NV3;
if (NormalsFromUV) {
NV1.SetXYZ(Normals(N1).XYZ());
NV2.SetXYZ(Normals(N2).XYZ());
NV3.SetXYZ(Normals(N3).XYZ());
}
else {
gp_Vec v1(V1.X(),V1.Y(),V1.Z()),
v2(V2.X(),V2.Y(),V2.Z()),
v3(V3.X(),V3.Y(),V3.Z());
gp_Vec normal = (v2-v1)^(v3-v1);
NV1 = normal;
NV2 = normal;
NV3 = normal;
}
// transform the vertices and normals to the place of the face
if (!identity) {
V1.Transform(myTransf);
V2.Transform(myTransf);
V3.Transform(myTransf);
if (NormalsFromUV) {
NV1.Transform(myTransf);
NV2.Transform(myTransf);
NV3.Transform(myTransf);
}
}
// add the normals for all points of this triangle
norms[faceNodeOffset+N1-1] += SbVec3f(NV1.X(),NV1.Y(),NV1.Z());
norms[faceNodeOffset+N2-1] += SbVec3f(NV2.X(),NV2.Y(),NV2.Z());
norms[faceNodeOffset+N3-1] += SbVec3f(NV3.X(),NV3.Y(),NV3.Z());
// set the vertices
verts[faceNodeOffset+N1-1].setValue((float)(V1.X()),(float)(V1.Y()),(float)(V1.Z()));
verts[faceNodeOffset+N2-1].setValue((float)(V2.X()),(float)(V2.Y()),(float)(V2.Z()));
verts[faceNodeOffset+N3-1].setValue((float)(V3.X()),(float)(V3.Y()),(float)(V3.Z()));
// set the index vector with the 3 point indexes and the end delimiter
index[faceTriaOffset*4+4*(g-1)] = faceNodeOffset+N1-1;
index[faceTriaOffset*4+4*(g-1)+1] = faceNodeOffset+N2-1;
index[faceTriaOffset*4+4*(g-1)+2] = faceNodeOffset+N3-1;
index[faceTriaOffset*4+4*(g-1)+3] = SO_END_FACE_INDEX;
}
parts[ii] = nbTriInFace; // new part
// handling the edges lying on this face
TopExp_Explorer Exp;
for(Exp.Init(actFace,TopAbs_EDGE);Exp.More();Exp.Next()) {
const TopoDS_Edge &curEdge = TopoDS::Edge(Exp.Current());
// get the overall index of this edge
int edgeIndex = edgeMap.FindIndex(curEdge);
edgeVector.push_back((int32_t)edgeIndex-1);
// already processed this index ?
if (edgeIdxSet.find(edgeIndex)!=edgeIdxSet.end()) {
// this holds the indices of the edge's triangulation to the current polygon
Handle(Poly_PolygonOnTriangulation) aPoly = BRep_Tool::PolygonOnTriangulation(curEdge, mesh, aLoc);
if (aPoly.IsNull())
continue; // polygon does not exist
// getting the indexes of the edge polygon
const TColStd_Array1OfInteger& indices = aPoly->Nodes();
for (Standard_Integer i=indices.Lower();i <= indices.Upper();i++) {
int nodeIndex = indices(i);
int index = faceNodeOffset+nodeIndex-1;
lineSetMap[edgeIndex].push_back(index);
// usually the coordinates for this edge are already set by the
// triangles of the face this edge belongs to. However, there are
// rare cases where some points are only referenced by the polygon
// but not by any triangle. Thus, we must apply the coordinates to
// make sure that everything is properly set.
#if OCC_VERSION_HEX < 0x070600
gp_Pnt p(Nodes(nodeIndex));
#else
gp_Pnt p(mesh->Node(nodeIndex));
#endif
if (!identity)
p.Transform(myTransf);
verts[index].setValue((float)(p.X()),(float)(p.Y()),(float)(p.Z()));
}
// remove the handled edge index from the set
edgeIdxSet.erase(edgeIndex);
}
}
edgeVector.push_back(-1);
// counting up the per Face offsets
faceNodeOffset += nbNodesInFace;
faceTriaOffset += nbTriInFace;
}
// handling of the free edges
for (int i=1; i <= edgeMap.Extent(); i++) {
const TopoDS_Edge& aEdge = TopoDS::Edge(edgeMap(i));
Standard_Boolean identity = true;
gp_Trsf myTransf;
TopLoc_Location aLoc;
// handling of the free edge that are not associated to a face
int hash = Part::ShapeMapHasher{}(aEdge);
if (faceEdges.find(hash) == faceEdges.end()) {
Handle(Poly_Polygon3D) aPoly = Part::Tools::polygonOfEdge(aEdge, aLoc);
if (!aPoly.IsNull()) {
if (!aLoc.IsIdentity()) {
identity = false;
myTransf = aLoc.Transformation();
}
const TColgp_Array1OfPnt& aNodes = aPoly->Nodes();
int nbNodesInEdge = aPoly->NbNodes();
gp_Pnt pnt;
for (Standard_Integer j=1;j <= nbNodesInEdge;j++) {
pnt = aNodes(j);
if (!identity)
pnt.Transform(myTransf);
int index = faceNodeOffset+j-1;
verts[index].setValue((float)(pnt.X()),(float)(pnt.Y()),(float)(pnt.Z()));
lineSetMap[i].push_back(index);
}
faceNodeOffset += nbNodesInEdge;
}
}
}
nodeset->startIndex.setValue(faceNodeOffset);
for (int i=0; i<vertexMap.Extent(); i++) {
const TopoDS_Vertex& aVertex = TopoDS::Vertex(vertexMap(i+1));
gp_Pnt pnt = BRep_Tool::Pnt(aVertex);
verts[faceNodeOffset+i].setValue((float)(pnt.X()),(float)(pnt.Y()),(float)(pnt.Z()));
}
// normalize all normals
for (int i = 0; i< numNorms ;i++)
norms[i].normalize();
std::vector<int32_t> lineSetCoords;
for (const auto & it : lineSetMap) {
lineSetCoords.insert(lineSetCoords.end(), it.second.begin(), it.second.end());
lineSetCoords.push_back(-1);
}
// preset the index vector size
numLines = lineSetCoords.size();
lineset ->coordIndex .setNum(numLines);
int32_t* lines = lineset ->coordIndex .startEditing();
int l=0;
for (std::vector<int32_t>::const_iterator it=lineSetCoords.begin();it!=lineSetCoords.end();++it,l++)
lines[l] = *it;
// end the editing of the nodes
coords ->point .finishEditing();
norm ->vector .finishEditing();
faceset ->coordIndex .finishEditing();
faceset ->partIndex .finishEditing();
lineset ->coordIndex .finishEditing();
VisualTouched = false;
}
catch (const Standard_Failure& e) {
FC_ERR("Cannot compute Inventor representation for the shape of "
<< pcObject->getFullName() << ": " << e.GetMessageString());
}
catch (...) {
FC_ERR("Cannot compute Inventor representation for the shape of " << pcObject->getFullName());
FC_ERR("Cannot compute Inventor representation for the shape of "
<< pcObject->getFullName());
}
# ifdef FC_DEBUG
// printing some information
Base::Console().log("ViewProvider update time: %f s\n",Base::TimeElapsed::diffTimeF(start_time,Base::TimeElapsed()));
Base::Console().log("Shape tria info: Faces:%d Edges:%d Nodes:%d Triangles:%d IdxVec:%d\n",numFaces,numEdges,numNodes,numTriangles,numLines);
# else
(void)numEdges;
# endif
VisualTouched = false;
// The material has to be checked again
setHighlightedFaces(ShapeAppearance.getValues());
setHighlightedEdges(LineColorArray.getValues());

View File

@@ -123,6 +123,10 @@ public:
std::vector<Base::Vector3d> getSelectionShape(const char* Element) const override;
//@}
virtual Part::TopoShape getRenderedShape() const {
return Part::Feature::getTopoShape(getObject(), Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform);
}
/** @name Highlight handling
* This group of methods do the highlighting of elements.
*/
@@ -156,6 +160,17 @@ public:
/// Get the python wrapper for that ViewProvider
PyObject* getPyObject() override;
/// configures Coin nodes so they render given toposhape
static void setupCoinGeometry(TopoDS_Shape shape,
SoCoordinate3* coords,
SoBrepFaceSet* faceset,
SoNormal* norm,
SoBrepEdgeSet* lineset,
SoBrepPointSet* nodeset,
double deviation,
double angularDeflection,
bool normalsFromUV);
protected:
bool setEdit(int ModNum) override;
void unsetEdit(int ModNum) override;

View File

@@ -0,0 +1,197 @@
// 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 "PreCompiled.h"
#ifndef _PreComp_
# 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>
#endif
#include "ViewProviderPreviewExtension.h"
#include "ViewProviderExt.h"
#include <Gui/Utilities.h>
#include <Gui/Inventor/So3DAnnotation.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()
: coords(new SoCoordinate3)
, norm(new SoNormal)
, faceset(new PartGui::SoBrepFaceSet)
, lineset(new PartGui::SoBrepEdgeSet)
, nodeset(new PartGui::SoBrepPointSet)
{
SO_NODE_CONSTRUCTOR(SoPreviewShape);
SO_NODE_ADD_FIELD(color, (defaultColor));
SO_NODE_ADD_FIELD(transparency, (defaultTransparency));
SO_NODE_ADD_FIELD(lineWidth, (defaultLineWidth));
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(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);
}
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);
pcPreviewShape = new SoPreviewShape;
pcPreviewRoot = new SoSeparator;
pcPreviewRoot->addChild(pcPreviewShape);
updatePreviewShape();
}
void ViewProviderPreviewExtension::extensionOnChanged(const App::Property* prop)
{
if (prop == &PreviewColor) {
pcPreviewShape->color.setValue(Base::convertTo<SbColor>(PreviewColor.getValue()));
}
ViewProviderExtension::extensionOnChanged(prop);
}
void ViewProviderPreviewExtension::updatePreviewShape()
{
auto vp = freecad_cast<ViewProviderPartExt*>(getExtendedViewProvider());
if (!vp) {
return;
}
ViewProviderPartExt::setupCoinGeometry(getPreviewShape().getShape(),
pcPreviewShape->coords,
pcPreviewShape->faceset,
pcPreviewShape->norm,
pcPreviewShape->lineset,
pcPreviewShape->nodeset,
vp->Deviation.getValue(),
vp->AngularDeflection.getValue(),
false);
// 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 = pcPreviewShape->lineset->coordIndex.getNum();
unsigned lineCount = 1;
for (unsigned i = 0; i < lineCoordsCount; ++i) {
if (pcPreviewShape->lineset->coordIndex[i] < 0) {
lineCount++;
}
}
pcPreviewShape->lineset->materialIndex.setNum(lineCount);
for (unsigned i = 0; i < lineCount; ++i) {
pcPreviewShape->lineset->materialIndex.set1Value(i, 0);
}
}
namespace Gui {
EXTENSION_PROPERTY_SOURCE_TEMPLATE(PartGui::ViewProviderPreviewExtensionPython, PartGui::ViewProviderPreviewExtension)
// explicit template instantiation
template class PartGuiExport ViewProviderExtensionPythonT<PartGui::ViewProviderPreviewExtension>;
}

View File

@@ -0,0 +1,110 @@
// 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/>. *
* *
***************************************************************************/
#ifndef PARTGUI_VIEWPROVIDERPREVIEWEXTENSION_H
#define PARTGUI_VIEWPROVIDERPREVIEWEXTENSION_H
#include "SoBrepEdgeSet.h"
#include "SoBrepFaceSet.h"
#include "SoBrepPointSet.h"
#include <QtCore>
#include <Inventor/nodes/SoSubNode.h>
#include <Inventor/nodes/SoCoordinate3.h>
#include <Inventor/nodes/SoNormal.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/fields/SoSFColor.h>
#include <Inventor/fields/SoSFFloat.h>
#include <App/PropertyStandard.h>
#include <Gui/ViewProvider.h>
#include <Gui/ViewProviderExtension.h>
#include <Gui/ViewProviderExtensionPython.h>
#include <Mod/Part/App/TopoShape.h>
namespace PartGui {
class PartGuiExport SoPreviewShape : public SoSeparator {
using inherited = SoSeparator;
SO_NODE_HEADER(SoPreviewShape);
public:
static constexpr float defaultTransparency = 0.8F;
static constexpr float defaultLineWidth = 2.0F;
static const SbColor defaultColor;
SoPreviewShape();
SoSFColor color;
SoSFFloat transparency;
SoSFFloat lineWidth;
SoCoordinate3* coords;
SoNormal* norm;
SoBrepFaceSet* faceset;
SoBrepEdgeSet* lineset;
SoBrepPointSet* nodeset;
};
class PartGuiExport ViewProviderPreviewExtension : public Gui::ViewProviderExtension {
Q_DECLARE_TR_FUNCTIONS(PartDesignGui::ViewProviderPreviewExtension)
EXTENSION_PROPERTY_HEADER_WITH_OVERRIDE(Gui::ViewProviderPreviewExtension);
public:
App::PropertyColor PreviewColor;
ViewProviderPreviewExtension();
/// Returns shape that should be used as the preview
virtual Part::TopoShape getPreviewShape() const { return Part::TopoShape(); };
void extensionAttach(App::DocumentObject*) override;
void extensionBeforeDelete() override;
/// Returns whatever preview is enabled or not
bool isPreviewEnabled() const { return _isPreviewEnabled; }
/// Switches preview on or off
virtual void showPreview(bool enable);
protected:
void extensionOnChanged(const App::Property* prop) override;
/// updates geometry of the preview shape
void updatePreviewShape();
Gui::CoinPtr<SoSeparator> pcPreviewRoot;
Gui::CoinPtr<SoPreviewShape> pcPreviewShape;
private:
bool _isPreviewEnabled {false};
};
using ViewProviderPreviewExtensionPython = Gui::ViewProviderExtensionPythonT<ViewProviderPreviewExtension>;
}
#endif // PARTGUI_VIEWPROVIDERPREVIEWEXTENSION_H