PartDesign: Add transparent previews
This commit is contained in:
@@ -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())){
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user