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

@@ -44,6 +44,8 @@
#include "Body.h"
#include "ShapeBinder.h"
#include <BRep_Builder.hxx>
FC_LOG_LEVEL_INIT("PartDesign", true, true)
@@ -70,6 +72,7 @@ Feature::Feature()
BaseFeature.setStatus(App::Property::Hidden, true);
App::SuppressibleExtension::initExtension(this);
Part::PreviewExtension::initExtension(this);
}
App::DocumentObjectExecReturn* Feature::recompute()
@@ -103,9 +106,17 @@ App::DocumentObjectExecReturn* Feature::recompute()
if (!failed) {
updateSuppressedShape();
}
return App::DocumentObject::StdReturn;
}
App::DocumentObjectExecReturn* Feature::recomputePreview()
{
updatePreviewShape();
return StdReturn;
}
void Feature::setMaterialToBodyMaterial()
{
auto body = getFeatureBody();
@@ -172,7 +183,8 @@ void Feature::onChanged(const App::Property *prop)
{
if (!this->isRestoring()
&& this->getDocument()
&& !this->getDocument()->isPerformingTransaction()) {
&& !this->getDocument()->isPerformingTransaction()
) {
if (prop == &Visibility || prop == &BaseFeature) {
auto body = Body::findBodyOf(this);
if (body) {
@@ -343,6 +355,51 @@ Part::TopoShape Feature::getBaseTopoShape(bool silent) const
return result;
}
void Feature::getGeneratedShapes(std::vector<int>& faces,
std::vector<int>& edges,
std::vector<int>& vertices) const
{
static const auto addAllSubShapesToSet = [](
const Part::TopoShape& shape,
const Part::TopoShape& face,
TopAbs_ShapeEnum type,
std::set<int>& set
) {
for (auto &subShape : face.getSubShapes(type)) {
if (int subShapeId = shape.findShape(subShape); subShapeId > 0) {
set.insert(subShapeId);
}
}
};
Part::TopoShape shape = Shape.getShape();
std::set<int> edgeSet;
std::set<int> vertexSet;
int count = shape.countSubShapes(TopAbs_FACE);
for (int faceId = 1; faceId <= count; ++faceId) {
if (Data::MappedName mapped = shape.getMappedName(Data::IndexedName::fromConst("Face", faceId));
shape.isElementGenerated(mapped)) {
faces.push_back(faceId);
Part::TopoShape face = shape.getSubTopoShape(TopAbs_FACE, faceId);
addAllSubShapesToSet(shape, face, TopAbs_EDGE, edgeSet);
addAllSubShapesToSet(shape, face, TopAbs_VERTEX, vertexSet);
}
}
std::ranges::copy(edgeSet, std::back_inserter(edges));
std::ranges::copy(vertexSet, std::back_inserter(vertices));
}
void Feature::updatePreviewShape()
{
// no-op
}
PyObject* Feature::getPyObject()
{
if (PythonObject.is(Py::_None())){

View File

@@ -27,6 +27,7 @@
#include <App/PropertyStandard.h>
#include <App/SuppressibleExtension.h>
#include <Mod/Part/App/PartFeature.h>
#include <Mod/Part/App/PreviewExtension.h>
#include <Mod/PartDesign/PartDesignGlobal.h>
class gp_Pnt;
@@ -45,7 +46,7 @@ class Body;
* Base class of all PartDesign features.
* This kind of features only produce solids or fail.
*/
class PartDesignExport Feature : public Part::Feature, public App::SuppressibleExtension
class PartDesignExport Feature : public Part::Feature, public App::SuppressibleExtension, public Part::PreviewExtension
{
PROPERTY_HEADER_WITH_OVERRIDE(PartDesign::Feature);
@@ -62,7 +63,9 @@ public:
Part::PropertyPartShape SuppressedShape;
/// Keep a copy of the placement before suppression to restore it back when unsuppressed, fix #20205
Base::Placement SuppressedPlacement;
App::DocumentObjectExecReturn* recompute() override;
App::DocumentObjectExecReturn* recomputePreview() override;
short mustExecute() const override;
@@ -84,6 +87,13 @@ public:
/// Returns the BaseFeature property's TopoShape (if any)
Part::TopoShape getBaseTopoShape(bool silent=false) const;
// Fills up information about which sub-shapes were generated by the feature
virtual void getGeneratedShapes(std::vector<int>& faces,
std::vector<int>& edges,
std::vector<int>& vertices) const;
virtual void updatePreviewShape();
PyObject* getPyObject() override;
const char* getViewProviderName() const override {

View File

@@ -28,10 +28,13 @@
#include <App/FeaturePythonPyImp.h>
#include <Mod/Part/App/modelRefine.h>
#include <Mod/Part/App/TopoShapeOpCode.h>
#include "FeatureAddSub.h"
#include "FeaturePy.h"
#include <Mod/Part/App/Tools.h>
using namespace PartDesign;
@@ -43,7 +46,12 @@ PROPERTY_SOURCE(PartDesign::FeatureAddSub, PartDesign::FeatureRefine)
FeatureAddSub::FeatureAddSub()
{
ADD_PROPERTY(AddSubShape,(TopoDS_Shape()));
ADD_PROPERTY(AddSubShape, (TopoDS_Shape()));
}
void FeatureAddSub::onChanged(const App::Property* property)
{
Feature::onChanged(property);
}
FeatureAddSub::Type FeatureAddSub::getAddSubType()
@@ -58,16 +66,53 @@ short FeatureAddSub::mustExecute() const
return PartDesign::Feature::mustExecute();
}
void FeatureAddSub::getAddSubShape(Part::TopoShape &addShape, Part::TopoShape &subShape)
void FeatureAddSub::getAddSubShape(Part::TopoShape& addShape, Part::TopoShape& subShape)
{
if (addSubType == Additive)
if (addSubType == Additive) {
addShape = AddSubShape.getShape();
else if (addSubType == Subtractive)
}
else if (addSubType == Subtractive) {
subShape = AddSubShape.getShape();
}
}
void FeatureAddSub::updatePreviewShape()
{
const auto notifyWarning = [](const QString& message) {
Base::Console().translatedUserWarning(
"Preview",
tr("Failure while computing removed volume preview: %1")
.arg(message)
.toUtf8());
};
// for subtractive shapes we want to also showcase removed volume, not only the tool
if (addSubType == Subtractive) {
TopoShape base = getBaseTopoShape(true).moved(getLocation().Inverted());
if (const TopoShape& addSubShape = AddSubShape.getShape(); !addSubShape.isEmpty()) {
try {
base.makeElementBoolean(Part::OpCodes::Common, { base, addSubShape });
} catch (Standard_Failure& e) {
notifyWarning(QString::fromUtf8(e.GetMessageString()));
} catch (Base::Exception& e) {
notifyWarning(QString::fromStdString(e.getMessage()));
}
if (base.isEmpty()) {
notifyWarning(tr("Resulting shape is empty. That may indicate that no material will be removed or a problem with the model."));
}
PreviewShape.setValue(base);
return;
}
}
PreviewShape.setValue(AddSubShape.getShape());
}
} // namespace PartDesign
namespace App {
/// @cond DOXERR
PROPERTY_SOURCE_TEMPLATE(PartDesign::FeatureAddSubPython, PartDesign::FeatureAddSub)

View File

@@ -26,12 +26,15 @@
#include "FeatureRefine.h"
#include <QtCore>
/// Base class of all additive features in PartDesign
namespace PartDesign
{
class PartDesignExport FeatureAddSub : public PartDesign::FeatureRefine
{
Q_DECLARE_TR_FUNCTIONS(PartDesign::FeatureAddSub)
PROPERTY_HEADER_WITH_OVERRIDE(PartDesign::FeatureAddSub);
public:
@@ -42,12 +45,15 @@ public:
FeatureAddSub();
void onChanged(const App::Property *) override;
Type getAddSubType();
short mustExecute() const override;
virtual void getAddSubShape(Part::TopoShape &addShape, Part::TopoShape &subShape);
void updatePreviewShape() override;
Part::PropertyPartShape AddSubShape;

View File

@@ -293,53 +293,62 @@ void DressUp::onChanged(const App::Property* prop)
Feature::onChanged(prop);
}
void DressUp::getAddSubShape(Part::TopoShape &addShape, Part::TopoShape &subShape)
void DressUp::getAddSubShape(Part::TopoShape& addShape, Part::TopoShape& subShape)
{
Part::TopoShape res = AddSubShape.getShape();
if(res.isNull()) {
if (res.isNull()) {
try {
std::vector<Part::TopoShape> shapes;
Part::TopoShape shape = Shape.getShape();
shape.setPlacement(Base::Placement());
FeatureAddSub *base = nullptr;
if(SupportTransform.getValue()) {
FeatureAddSub* base = nullptr;
if (SupportTransform.getValue()) {
// SupportTransform means transform the support together with
// the dressing. So we need to find the previous support
// feature (which must be of type FeatureAddSub), and skipping
// any consecutive DressUp in-between.
for(Feature *current=this; ;current=static_cast<DressUp*>(base)) {
for (Feature* current = this;; current = static_cast<DressUp*>(base)) {
base = freecad_cast<FeatureAddSub*>(current->getBaseObject(true));
if(!base)
if (!base) {
FC_THROWM(Base::CADKernelError,
"Cannot find additive or subtractive support for " << getFullName());
if(!base->isDerivedFrom<DressUp>())
"Cannot find additive or subtractive support for "
<< getFullName());
}
if (!base->isDerivedFrom<DressUp>()) {
break;
}
}
}
Part::TopoShape baseShape;
if(base) {
if (base) {
baseShape = base->getBaseTopoShape(true);
baseShape.move(base->getLocation().Inverted());
if (base->getAddSubType() == Additive) {
if(!baseShape.isNull() && baseShape.hasSubShape(TopAbs_SOLID))
if (!baseShape.isNull() && baseShape.hasSubShape(TopAbs_SOLID)) {
shapes.emplace_back(shape.makeElementCut(baseShape.getShape()));
else
}
else {
shapes.push_back(shape);
} else {
}
}
else {
BRep_Builder builder;
TopoDS_Compound comp;
builder.MakeCompound(comp);
// push an empty compound to indicate null additive shape
shapes.emplace_back(comp);
if(!baseShape.isNull() && baseShape.hasSubShape(TopAbs_SOLID))
if (!baseShape.isNull() && baseShape.hasSubShape(TopAbs_SOLID)) {
shapes.emplace_back(baseShape.makeElementCut(shape.getShape()));
else
}
else {
shapes.push_back(shape);
}
}
} else {
}
else {
baseShape = getBaseTopoShape();
baseShape.move(getLocation().Inverted());
shapes.emplace_back(shape.makeElementCut(baseShape.getShape()));
@@ -350,34 +359,73 @@ void DressUp::getAddSubShape(Part::TopoShape &addShape, Part::TopoShape &subShap
// bceause a dressing (e.g. a fillet) can either be additive or
// subtractive. And the dressup feature can contain mixture of both.
AddSubShape.setValue(Part::TopoShape().makeElementCompound(shapes));
} catch (Standard_Failure &e) {
FC_THROWM(Base::CADKernelError, "Failed to calculate AddSub shape: "
<< e.GetMessageString());
}
catch (Standard_Failure& e) {
FC_THROWM(Base::CADKernelError,
"Failed to calculate AddSub shape: " << e.GetMessageString());
}
res = AddSubShape.getShape();
}
if(res.isNull())
if (res.isNull()) {
throw Part::NullShapeException("Null AddSub shape");
}
if(res.getShape().ShapeType() != TopAbs_COMPOUND) {
if (res.getShape().ShapeType() != TopAbs_COMPOUND) {
addShape = res;
} else {
}
else {
int count = res.countSubShapes(TopAbs_SHAPE);
if(!count)
if (!count) {
throw Part::NullShapeException("Null AddSub shape");
if(count) {
Part::TopoShape s = res.getSubTopoShape(TopAbs_SHAPE, 1);
if(!s.isNull() && s.hasSubShape(TopAbs_SOLID))
addShape = s;
}
if(count > 1) {
if (count) {
Part::TopoShape s = res.getSubTopoShape(TopAbs_SHAPE, 1);
if (!s.isNull() && s.hasSubShape(TopAbs_SOLID)) {
addShape = s;
}
}
if (count > 1) {
Part::TopoShape s = res.getSubTopoShape(TopAbs_SHAPE, 2);
if(!s.isNull() && s.hasSubShape(TopAbs_SOLID))
if (!s.isNull() && s.hasSubShape(TopAbs_SOLID)) {
subShape = s;
}
}
}
}
void DressUp::updatePreviewShape()
{
auto shape = Shape.getShape();
auto baseFeature = freecad_cast<Feature*>(BaseFeature.getValue());
if (!baseFeature || baseFeature->Shape.getShape().isNull()) {
PreviewShape.setValue(Shape.getShape());
return;
}
std::vector<int> faces, edges, vertices;
getGeneratedShapes(faces, edges, vertices);
if (faces.empty()) {
PreviewShape.setValue(TopoDS_Shape());
return;
}
shape.setPlacement(Base::Placement());
BRep_Builder builder;
TopoDS_Compound comp;
builder.MakeCompound(comp);
for (int faceId : faces) {
builder.Add(comp, shape.getSubShape(TopAbs_FACE, faceId));
}
Part::TopoShape preview(comp);
preview.mapSubElement(shape);
PreviewShape.setValue(preview);
}
} // namespace PartDesign

View File

@@ -65,6 +65,7 @@ public:
std::vector<TopoShape> getFaces(const TopoShape &shape);
void getAddSubShape(Part::TopoShape &addShape, Part::TopoShape &subShape) override;
void updatePreviewShape() override;
protected:
void onChanged(const App::Property* prop) override;

View File

@@ -150,10 +150,11 @@ App::DocumentObjectExecReturn* FeaturePrimitive::execute(const TopoDS_Shape& pri
void FeaturePrimitive::onChanged(const App::Property* prop)
{
if (prop == &AttachmentOffset){
this->touch();
if (prop == &AttachmentOffset) {
this->recompute();
return;
}
FeatureAddSub::onChanged(prop);
}