diff --git a/src/Mod/PartDesign/App/Body.cpp b/src/Mod/PartDesign/App/Body.cpp index 2a89ec9762..930b160ba0 100644 --- a/src/Mod/PartDesign/App/Body.cpp +++ b/src/Mod/PartDesign/App/Body.cpp @@ -184,35 +184,22 @@ bool Body::isAfterInsertPoint(App::DocumentObject* feature) { } } -bool Body::isMemberOfMultiTransform(const App::DocumentObject* obj) -{ - if (!obj) - return false; - - // ORIGINAL COMMENT: - // This can be recognized because the Originals property is empty (it is contained - // in the MultiTransform instead) - // COMMENT ON THE COMMENT: - // This is wrong because at the creation (addObject) and before assigning the originals, that - // is when this code is executed, the originals property is indeed empty. - // - // However, for the purpose of setting the base feature, the transform feature has been modified - // to auto set it when the originals are not null. See: - // App::DocumentObjectExecReturn *Transformed::execute(void) - // - return (obj->isDerivedFrom() && - static_cast(obj)->Originals.getValues().empty()); -} - bool Body::isSolidFeature(const App::DocumentObject *obj) { - if (!obj) + if (!obj) { return false; + } - if (obj->isDerivedFrom() && - !PartDesign::Feature::isDatum(obj)) { - // Transformed Features inside a MultiTransform are not solid features - return !isMemberOfMultiTransform(obj); + if (obj->isDerivedFrom()) { + if (PartDesign::Feature::isDatum(obj)) { + // Datum objects are not solid + return false; + } + if (auto transFeature = Base::freecad_dynamic_cast(obj)) { + // Transformed Features inside a MultiTransform are not solid features + return !transFeature->isMultiTransformChild(); + } + return true; } return false;//DeepSOIC: work-in-progress? } diff --git a/src/Mod/PartDesign/App/Body.h b/src/Mod/PartDesign/App/Body.h index e36f751960..b905fd2997 100644 --- a/src/Mod/PartDesign/App/Body.h +++ b/src/Mod/PartDesign/App/Body.h @@ -91,9 +91,6 @@ public: */ bool isAfterInsertPoint(App::DocumentObject* feature); - /// Return true if the given feature is member of a MultiTransform feature - static bool isMemberOfMultiTransform(const App::DocumentObject *obj); - /** * Return true if the given feature is a solid feature allowed in a Body. Currently this is only valid * for features derived from PartDesign::Feature diff --git a/src/Mod/PartDesign/App/FeatureMultiTransform.cpp b/src/Mod/PartDesign/App/FeatureMultiTransform.cpp index b4ac76d554..14cc57177b 100644 --- a/src/Mod/PartDesign/App/FeatureMultiTransform.cpp +++ b/src/Mod/PartDesign/App/FeatureMultiTransform.cpp @@ -23,9 +23,9 @@ #include "PreCompiled.h" #ifndef _PreComp_ -# include -# include -# include +#include +#include +#include #endif #include "FeatureMultiTransform.h" @@ -35,14 +35,15 @@ using namespace PartDesign; -namespace PartDesign { +namespace PartDesign +{ PROPERTY_SOURCE(PartDesign::MultiTransform, PartDesign::Transformed) MultiTransform::MultiTransform() { - ADD_PROPERTY(Transformations,(nullptr)); + ADD_PROPERTY(Transformations, (nullptr)); Transformations.setSize(0); } @@ -51,9 +52,11 @@ void MultiTransform::positionBySupport() PartDesign::Transformed::positionBySupport(); std::vector transFeatures = Transformations.getValues(); for (auto f : transFeatures) { - if (!(f->isDerivedFrom())) + auto transFeature = Base::freecad_dynamic_cast(f); + if (!transFeature) { throw Base::TypeError("Transformation features must be subclasses of Transformed"); - PartDesign::Transformed* transFeature = static_cast(f); + } + transFeature->Placement.setValue(this->Placement.getValue()); // To avoid that a linked transform feature stays touched after a recompute @@ -66,57 +69,57 @@ void MultiTransform::positionBySupport() short MultiTransform::mustExecute() const { - if (Transformations.isTouched()) + if (Transformations.isTouched()) { return 1; + } return Transformed::mustExecute(); } -const std::list MultiTransform::getTransformations(const std::vector originals) +const std::list +MultiTransform::getTransformations(const std::vector originals) { std::vector transFeatures = Transformations.getValues(); - // Find centre of gravity of first original - // FIXME: This method will NOT give the expected result for more than one original! - Part::Feature* originalFeature = static_cast(originals.front()); - TopoDS_Shape original; + gp_Pnt cog; + if (!originals.empty()) { + // Find centre of gravity of first original + // FIXME: This method will NOT give the expected result for more than one original! + if (auto addFeature = + Base::freecad_dynamic_cast(originals.front())) { + TopoDS_Shape original = addFeature->AddSubShape.getShape().getShape(); - if (originalFeature->isDerivedFrom()) { - PartDesign::FeatureAddSub* addFeature = static_cast(originalFeature); - //if (addFeature->getAddSubType() == FeatureAddSub::Additive) - // original = addFeature->AddSubShape.getShape().getShape(); - //else - original = addFeature->AddSubShape.getShape().getShape(); + GProp_GProps props; + BRepGProp::VolumeProperties(original, props); + cog = props.CentreOfMass(); + } } - GProp_GProps props; - BRepGProp::VolumeProperties(original,props); - gp_Pnt cog = props.CentreOfMass(); - std::list result; std::list cogs; - std::vector::const_iterator f; - for (f = transFeatures.begin(); f != transFeatures.end(); ++f) { - if (!((*f)->isDerivedFrom())) + for (auto const& f : transFeatures) { + auto transFeature = Base::freecad_dynamic_cast(f); + if (!transFeature) { throw Base::TypeError("Transformation features must be subclasses of Transformed"); - PartDesign::Transformed* transFeature = static_cast(*f); - std::list newTransformations = transFeature->getTransformations(originals); + } + std::list newTransformations = transFeature->getTransformations(originals); if (result.empty()) { // First transformation Feature result = newTransformations; - for (std::list::const_iterator nt = newTransformations.begin(); nt != newTransformations.end(); ++nt) { - cogs.push_back(cog.Transformed(*nt)); + for (auto nt : newTransformations) { + cogs.push_back(cog.Transformed(nt)); } - } else { + } + else { // Retain a copy of the first set of transformations for iterator ot // We can't iterate through result if we are also adding elements with push_back()! std::list oldTransformations; - result.swap(oldTransformations); // empty result to receive new transformations + result.swap(oldTransformations); // empty result to receive new transformations std::list oldCogs; - cogs.swap(oldCogs); // empty cogs to receive new cogs + cogs.swap(oldCogs); // empty cogs to receive new cogs - if ((*f)->is()) { + if (transFeature->is()) { // Diagonal method // Multiply every element in the old transformations' slices with the corresponding // element in the newTransformations. Example: @@ -126,57 +129,64 @@ const std::list MultiTransform::getTransformations(const std::vector::const_iterator ot = oldTransformations.begin(); - std::list::const_iterator oc = oldCogs.begin(); + auto ot = oldTransformations.begin(); + auto oc = oldCogs.begin(); - for (std::list::const_iterator nt = newTransformations.begin(); nt != newTransformations.end(); ++nt) { + for (auto const& nt : newTransformations) { for (unsigned s = 0; s < sliceLength; s++) { gp_Trsf trans; - double factor = nt->ScaleFactor(); // extract scale factor + double factor = nt.ScaleFactor(); // extract scale factor if (factor > Precision::Confusion()) { - trans.SetScale(*oc, factor); // recreate the scaled transformation to use the correct COG + trans.SetScale(*oc, factor); // recreate the scaled transformation to + // use the correct COG trans = trans * (*ot); - cogs.push_back(*oc); // Scaling does not affect the COG - } else { - trans = (*nt) * (*ot); - cogs.push_back(oc->Transformed(*nt)); + cogs.push_back(*oc); // Scaling does not affect the COG + } + else { + trans = nt * (*ot); + cogs.push_back(oc->Transformed(nt)); } result.push_back(trans); ++ot; ++oc; } } - } else { + } + else { // Multiplication method: Combine the new transformations with the old ones. - // All old transformations are multiplied with all new ones, so that the length of the - // result vector is the length of the old and new transformations multiplied. + // All old transformations are multiplied with all new ones, so that the length of + // the result vector is the length of the old and new transformations multiplied. // a11 a12 b1 a11*b1 a12*b1 a11*b2 a12*b2 a11*b3 a12*b3 // a21 a22 mul b2 = a21*b1 a22*b1 a21*b2 a22*b2 a21*b3 a22*b3 // b3 - for (std::list::const_iterator nt = newTransformations.begin(); nt != newTransformations.end(); ++nt) { - std::list::const_iterator oc = oldCogs.begin(); + for (auto const& nt : newTransformations) { + auto oc = oldCogs.begin(); - for (std::list::const_iterator ot = oldTransformations.begin(); ot != oldTransformations.end(); ++ot) { - result.push_back((*nt) * (*ot)); - cogs.push_back(oc->Transformed(*nt)); + for (auto const& ot : oldTransformations) { + result.push_back(nt * ot); + cogs.push_back(oc->Transformed(nt)); ++oc; } } } - // What about the Additive method: Take the last (set of) transformations and use them as - // "originals" for the next transformationFeature, so that something similar to a sweep - // for transformations could be put together? + // What about the Additive method: Take the last (set of) transformations and use them + // as "originals" for the next transformationFeature, so that something similar to a + // sweep for transformations could be put together? } } return result; } -} +} // namespace PartDesign diff --git a/src/Mod/PartDesign/App/FeatureMultiTransform.h b/src/Mod/PartDesign/App/FeatureMultiTransform.h index b2ceb85693..bfe1e0a658 100644 --- a/src/Mod/PartDesign/App/FeatureMultiTransform.h +++ b/src/Mod/PartDesign/App/FeatureMultiTransform.h @@ -30,7 +30,7 @@ namespace PartDesign { -class PartDesignExport MultiTransform : public PartDesign::Transformed +class PartDesignExport MultiTransform: public PartDesign::Transformed { PROPERTY_HEADER_WITH_OVERRIDE(PartDesign::MultiTransform); @@ -39,30 +39,35 @@ public: App::PropertyLinkList Transformations; - /** @name methods override feature */ + /** @name methods override feature */ //@{ short mustExecute() const override; /// returns the type name of the view provider - const char* getViewProviderName() const override { + const char* getViewProviderName() const override + { return "PartDesignGui::ViewProviderMultiTransform"; } //@} - std::vector getOriginals() const { return Originals.getValues(); } + std::vector getOriginals() const + { + return Originals.getValues(); + } /** Create transformations - * Returns a list containing the product of all transformations of the subfeatures given - * by the Transformations property. Subfeatures can be Mirrored, LinearPattern, PolarPattern and - * Scaled. - */ - const std::list getTransformations(const std::vector originals) override; + * Returns a list containing the product of all transformations of the subfeatures given + * by the Transformations property. Subfeatures can be Mirrored, LinearPattern, PolarPattern and + * Scaled. + */ + const std::list + getTransformations(const std::vector originals) override; protected: void positionBySupport() override; }; -} //namespace PartDesign +} // namespace PartDesign -#endif // PARTDESIGN_FeatureMultiTransform_H +#endif // PARTDESIGN_FeatureMultiTransform_H diff --git a/src/Mod/PartDesign/App/FeatureScaled.cpp b/src/Mod/PartDesign/App/FeatureScaled.cpp index 7cc1717e89..a6a0b8fd7d 100644 --- a/src/Mod/PartDesign/App/FeatureScaled.cpp +++ b/src/Mod/PartDesign/App/FeatureScaled.cpp @@ -23,9 +23,9 @@ #include "PreCompiled.h" #ifndef _PreComp_ -# include -# include -# include +#include +#include +#include #endif #include "FeatureScaled.h" @@ -34,58 +34,58 @@ using namespace PartDesign; -namespace PartDesign { +namespace PartDesign +{ PROPERTY_SOURCE(PartDesign::Scaled, PartDesign::Transformed) Scaled::Scaled() { - ADD_PROPERTY(Factor,(2.0)); - ADD_PROPERTY(Occurrences,(2)); + ADD_PROPERTY(Factor, (2.0)); + ADD_PROPERTY(Occurrences, (2)); } short Scaled::mustExecute() const { - if (Factor.isTouched() || - Occurrences.isTouched()) + if (Factor.isTouched() || Occurrences.isTouched()) { return 1; + } return Transformed::mustExecute(); } -const std::list Scaled::getTransformations(const std::vector originals) +const std::list +Scaled::getTransformations(const std::vector originals) { - double factor = Factor.getValue(); - if (factor < Precision::Confusion()) + double const factor = Factor.getValue(); + if (factor < Precision::Confusion()) { throw Base::ValueError("Scaling factor too small"); - int occurrences = Occurrences.getValue(); - if (occurrences < 2) + } + int const occurrences = Occurrences.getValue(); + if (occurrences < 2) { throw Base::ValueError("At least two occurrences required"); + } double f = (factor - 1.0) / double(occurrences - 1); - // Find centre of gravity of first original - // FIXME: This method will NOT give the expected result for more than one original! - Part::Feature* originalFeature = static_cast(originals.front()); - TopoDS_Shape original; + gp_Pnt cog; + if (!originals.empty()) { + // Find centre of gravity of first original + // FIXME: This method will NOT give the expected result for more than one original! + if (auto feature = Base::freecad_dynamic_cast(originals.front())) { + TopoDS_Shape original = feature->AddSubShape.getShape().getShape(); - if (originalFeature->isDerivedFrom()) { - PartDesign::FeatureAddSub* Feature = static_cast(originalFeature); - //if(Feature->getAddSubType() == FeatureAddSub::Additive) - // original = Feature->AddSubShape.getShape().getShape(); - //else - original = Feature->AddSubShape.getShape().getShape(); + GProp_GProps props; + BRepGProp::VolumeProperties(original, props); + cog = props.CentreOfMass(); + } } - GProp_GProps props; - BRepGProp::VolumeProperties(original,props); - gp_Pnt cog = props.CentreOfMass(); - // Note: The original feature is NOT included in the list of transformations! Therefore // we start with occurrence number 1, not number 0 std::list transformations; gp_Trsf trans; - transformations.push_back(trans); // identity transformation + transformations.push_back(trans); // identity transformation for (int i = 1; i < occurrences; i++) { trans.SetScale(cog, 1.0 + double(i) * f); @@ -95,4 +95,4 @@ const std::list Scaled::getTransformations(const std::vector getTransformations(const std::vector originals) override; + const std::list + getTransformations(const std::vector originals) override; }; -} //namespace PartDesign +} // namespace PartDesign -#endif // PARTDESIGN_FeatureScaled_H +#endif // PARTDESIGN_FeatureScaled_H diff --git a/src/Mod/PartDesign/App/FeatureTransformed.cpp b/src/Mod/PartDesign/App/FeatureTransformed.cpp index 7656e15d7d..fe68f30155 100644 --- a/src/Mod/PartDesign/App/FeatureTransformed.cpp +++ b/src/Mod/PartDesign/App/FeatureTransformed.cpp @@ -22,17 +22,19 @@ #include "PreCompiled.h" #ifndef _PreComp_ -# include -# include -# include -# include -# include -# include -# include -# include -# include +#include +#include +#include +#include +#include +#include +#include +#include +#include #endif +#include + #include #include #include @@ -43,6 +45,7 @@ #include "FeatureTransformed.h" #include "Body.h" #include "FeatureAddSub.h" +#include "FeatureMultiTransform.h" #include "FeatureMirrored.h" #include "FeatureLinearPattern.h" #include "FeaturePolarPattern.h" @@ -51,49 +54,68 @@ using namespace PartDesign; -namespace PartDesign { +namespace PartDesign +{ PROPERTY_SOURCE(PartDesign::Transformed, PartDesign::Feature) +std::array transformModeEnums = {"Transform tool shapes", + "Transform body", + nullptr}; + Transformed::Transformed() { - ADD_PROPERTY(Originals,(nullptr)); + ADD_PROPERTY(Originals, (nullptr)); Originals.setSize(0); Placement.setStatus(App::Property::ReadOnly, true); - ADD_PROPERTY_TYPE(Refine,(0),"Part Design",(App::PropertyType)(App::Prop_None),"Refine shape (clean up redundant edges) after adding/subtracting"); + ADD_PROPERTY(TransformMode, (static_cast(Mode::TransformToolShapes))); + TransformMode.setEnums(transformModeEnums.data()); - //init Refine property - Base::Reference hGrp = App::GetApplication().GetUserParameter() - .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/PartDesign"); + ADD_PROPERTY_TYPE(Refine, + (0), + "Part Design", + (App::PropertyType)(App::Prop_None), + "Refine shape (clean up redundant edges) after adding/subtracting"); + + // init Refine property + Base::Reference hGrp = App::GetApplication() + .GetUserParameter() + .GetGroup("BaseApp") + ->GetGroup("Preferences") + ->GetGroup("Mod/PartDesign"); this->Refine.setValue(hGrp->GetBool("RefineModel", false)); } void Transformed::positionBySupport() { // TODO May be here better to throw exception (silent=false) (2015-07-27, Fat-Zer) - Part::Feature *support = getBaseObject(/* silent =*/ true); - if (support) + Part::Feature* support = getBaseObject(/* silent =*/true); + if (support) { this->Placement.setValue(support->Placement.getValue()); + } } -Part::Feature* Transformed::getBaseObject(bool silent) const { - Part::Feature *rv = Feature::getBaseObject(/* silent = */ true); +Part::Feature* Transformed::getBaseObject(bool silent) const +{ + Part::Feature* rv = Feature::getBaseObject(/* silent = */ true); if (rv) { return rv; } const char* err = nullptr; - const std::vector & originals = Originals.getValues(); - // NOTE: may be here supposed to be last origin but in order to keep the old behaviour keep here first + const std::vector& originals = Originals.getValues(); + // NOTE: may be here supposed to be last origin but in order to keep the old behaviour keep here + // first App::DocumentObject* firstOriginal = originals.empty() ? nullptr : originals.front(); if (firstOriginal) { - if(firstOriginal->isDerivedFrom(Part::Feature::getClassTypeId())) { - rv = static_cast(firstOriginal); - } else { - err = QT_TRANSLATE_NOOP("Exception", "Transformation feature Linked object is not a Part object"); + rv = Base::freecad_dynamic_cast(firstOriginal); + if (!rv) { + err = QT_TRANSLATE_NOOP("Exception", + "Transformation feature Linked object is not a Part object"); } - } else { + } + else { err = QT_TRANSLATE_NOOP("Exception", "No originals linked to the transformed feature."); } @@ -107,49 +129,73 @@ Part::Feature* Transformed::getBaseObject(bool silent) const { App::DocumentObject* Transformed::getSketchObject() const { std::vector originals = Originals.getValues(); - if (!originals.empty() && originals.front()->isDerivedFrom()) { - return (static_cast(originals.front()))->getVerifiedSketch(true); + DocumentObject const* firstOriginal = !originals.empty() ? originals.front() : nullptr; + + if (auto feature = Base::freecad_dynamic_cast(firstOriginal)) { + return feature->getVerifiedSketch(true); } - else if (!originals.empty() && originals.front()->isDerivedFrom()) { + if (Base::freecad_dynamic_cast(firstOriginal)) { return nullptr; } - else if (this->isDerivedFrom()) { - // if Originals is empty then try the linear pattern's Direction property - const LinearPattern* pattern = static_cast(this); + if (auto pattern = Base::freecad_dynamic_cast(this)) { return pattern->Direction.getValue(); } - else if (this->isDerivedFrom()) { - // if Originals is empty then try the polar pattern's Axis property - const PolarPattern* pattern = static_cast(this); + if (auto pattern = Base::freecad_dynamic_cast(this)) { return pattern->Axis.getValue(); } - else if (this->isDerivedFrom()) { - // if Originals is empty then try the mirror pattern's MirrorPlane property - const Mirrored* pattern = static_cast(this); + if (auto pattern = Base::freecad_dynamic_cast(this)) { return pattern->MirrorPlane.getValue(); } - else { - return nullptr; - } + + return nullptr; } -void Transformed::Restore(Base::XMLReader &reader) +void Transformed::Restore(Base::XMLReader& reader) { PartDesign::Feature::Restore(reader); } -void Transformed::handleChangedPropertyType(Base::XMLReader &reader, const char * TypeName, App::Property * prop) +bool Transformed::isMultiTransformChild() const +{ + // Checking for a MultiTransform in the dependency list is not reliable on initialization + // because the dependencies are only established after creation. + /* + for (auto const* obj : getInList()) { + auto mt = Base::freecad_dynamic_cast(obj); + if (!mt) { + continue; + } + + auto const transfmt = mt->Transformations.getValues(); + if (std::find(transfmt.begin(), transfmt.end(), this) != transfmt.end()) { + return true; + } + } + */ + + // instead check for default property values because these are invalid for a standalone transform feature. + // This will mislabel standalone features during the initialization phase. + if (TransformMode.getValue() == 0 && Originals.getValue().empty()) { + return true; + } + + return false; +} + +void Transformed::handleChangedPropertyType(Base::XMLReader& reader, + const char* TypeName, + App::Property* prop) { // The property 'Angle' of PolarPattern has changed from PropertyFloat // to PropertyAngle and the property 'Length' has changed to PropertyLength. Base::Type inputType = Base::Type::fromName(TypeName); - if (prop->isDerivedFrom() && - inputType.isDerivedFrom(App::PropertyFloat::getClassTypeId())) { + if (auto property = Base::freecad_dynamic_cast(prop); + property != nullptr && inputType.isDerivedFrom(App::PropertyFloat::getClassTypeId())) { // Do not directly call the property's Restore method in case the implementation // has changed. So, create a temporary PropertyFloat object and assign the value. App::PropertyFloat floatProp; floatProp.Restore(reader); - static_cast(prop)->setValue(floatProp.getValue()); + property->setValue(floatProp.getValue()); } else { PartDesign::Feature::handleChangedPropertyType(reader, TypeName, prop); @@ -158,35 +204,48 @@ void Transformed::handleChangedPropertyType(Base::XMLReader &reader, const char short Transformed::mustExecute() const { - if (Originals.isTouched()) + if (Originals.isTouched() || TransformMode.isTouched()) { return 1; + } return PartDesign::Feature::mustExecute(); } -App::DocumentObjectExecReturn *Transformed::execute() +App::DocumentObjectExecReturn* Transformed::execute() { - std::vector originals = Originals.getValues(); - if (originals.empty()) // typically InsideMultiTransform + if (isMultiTransformChild()) { return App::DocumentObject::StdReturn; + } - if(!this->BaseFeature.getValue()) { + auto const mode = static_cast(TransformMode.getValue()); + if (mode == Mode::TransformBody) { + Originals.setValues({}); + } + + std::vector originals = Originals.getValues(); + // Remove suppressed features from the list so the transformations behave as if they are not + // there + { + auto eraseIter = + std::remove_if(originals.begin(), originals.end(), [](App::DocumentObject const* obj) { + auto feature = Base::freecad_dynamic_cast(obj); + return feature != nullptr && feature->Suppressed.getValue(); + }); + originals.erase(eraseIter, originals.end()); + } + + if (mode == Mode::TransformToolShapes && originals.empty()) { + return App::DocumentObject::StdReturn; + } + + if (!this->BaseFeature.getValue()) { auto body = getFeatureBody(); - if(body) { + if (body) { body->setBaseProperty(this); } } this->positionBySupport(); - // Remove suppressed features from the list so the transformations behave as if they are not there - { - auto eraseIter = std::remove_if(originals.begin(), originals.end(), [](App::DocumentObject const* obj) { - auto feature = Base::freecad_dynamic_cast(obj); - return feature != nullptr && feature->Suppressed.getValue(); - }); - originals.erase(eraseIter, originals.end()); - } - // get transformations from subclass by calling virtual method std::vector transformations; try { @@ -200,21 +259,25 @@ App::DocumentObjectExecReturn *Transformed::execute() return new App::DocumentObjectExecReturn(e.GetMessageString()); } - if (transformations.empty()) - return App::DocumentObject::StdReturn; // No transformations defined, exit silently + if (transformations.empty()) { + return App::DocumentObject::StdReturn; // No transformations defined, exit silently + } // Get the support - Part::Feature* supportFeature; + Part::Feature* supportFeature = nullptr; try { supportFeature = getBaseObject(); - } catch (Base::Exception& e) { + } + catch (Base::Exception& e) { return new App::DocumentObjectExecReturn(e.what()); } const Part::TopoShape& supportTopShape = supportFeature->Shape.getShape(); - if (supportTopShape.getShape().IsNull()) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Cannot transform invalid support shape")); + if (supportTopShape.getShape().IsNull()) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Cannot transform invalid support shape")); + } // create an untransformed copy of the support shape Part::TopoShape supportShape(supportTopShape); @@ -224,24 +287,23 @@ App::DocumentObjectExecReturn *Transformed::execute() supportShape.setTransform(Base::Matrix4D()); TopoDS_Shape support = supportShape.getShape(); - auto getTransformedCompShape = [&](const auto& origShape) - { + auto getTransformedCompShape = [&](const auto& origShape) { TopTools_ListOfShape shapeTools; std::vector shapes; - std::vector::const_iterator transformIter = transformations.begin(); + auto transformIter = transformations.cbegin(); // First transformation is skipped since it should not be part of the toolShape. ++transformIter; for (; transformIter != transformations.end(); ++transformIter) { - // Make an explicit copy of the shape because the "true" parameter to BRepBuilderAPI_Transform - // seems to be pretty broken + // Make an explicit copy of the shape because the "true" parameter to + // BRepBuilderAPI_Transform seems to be pretty broken BRepBuilderAPI_Copy copy(origShape); TopoDS_Shape shape = copy.Shape(); - BRepBuilderAPI_Transform mkTrf(shape, *transformIter, false); // No need to copy, now + BRepBuilderAPI_Transform mkTrf(shape, *transformIter, false); // No need to copy, now if (!mkTrf.IsDone()) { throw Base::CADKernelError(QT_TRANSLATE_NOOP("Exception", "Transformation failed")); } @@ -250,79 +312,111 @@ App::DocumentObjectExecReturn *Transformed::execute() shapes.emplace_back(shape); } - for (const auto& shape : shapes) + for (const auto& shape : shapes) { shapeTools.Append(shape); + } return shapeTools; }; - // NOTE: It would be possible to build a compound from all original addShapes/subShapes and then - // transform the compounds as a whole. But we choose to apply the transformations to each - // Original separately. This way it is easier to discover what feature causes a fuse/cut - // to fail. The downside is that performance suffers when there are many originals. But it seems - // safe to assume that in most cases there are few originals and many transformations - for (auto original : originals) - { - // Extract the original shape and determine whether to cut or to fuse - Part::TopoShape fuseShape; - Part::TopoShape cutShape; + switch (mode) { + case Mode::TransformToolShapes: + // NOTE: It would be possible to build a compound from all original addShapes/subShapes + // and then transform the compounds as a whole. But we choose to apply the + // transformations to each Original separately. This way it is easier to discover what + // feature causes a fuse/cut to fail. The downside is that performance suffers when + // there are many originals. But it seems safe to assume that in most cases there are + // few originals and many transformations + for (auto original : originals) { + // Extract the original shape and determine whether to cut or to fuse + Part::TopoShape fuseShape; + Part::TopoShape cutShape; - if (original->isDerivedFrom()) { - PartDesign::FeatureAddSub* feature = static_cast(original); - feature->getAddSubShape(fuseShape, cutShape); - if (fuseShape.isNull() && cutShape.isNull()) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Shape of additive/subtractive feature is empty")); - gp_Trsf trsf = feature->getLocation().Transformation().Multiplied(trsfInv); + auto feature = Base::freecad_dynamic_cast(original); + if (!feature) { + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP( + "Exception", + "Only additive and subtractive features can be transformed")); + } + + feature->getAddSubShape(fuseShape, cutShape); + if (fuseShape.isNull() && cutShape.isNull()) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", + "Shape of additive/subtractive feature is empty")); + } + gp_Trsf trsf = feature->getLocation().Transformation().Multiplied(trsfInv); #ifdef FC_USE_TNP_FIX - if (!fuseShape.isNull()) - fuseShape = fuseShape.makeElementTransform(trsf); - if (!cutShape.isNull()) - cutShape = cutShape.makeElementTransform(trsf); + if (!fuseShape.isNull()) { + fuseShape = fuseShape.makeElementTransform(trsf); + } + if (!cutShape.isNull()) { + cutShape = cutShape.makeElementTransform(trsf); + } #else - if (!fuseShape.isNull()) - fuseShape = fuseShape.makeTransform(trsf); - if (!cutShape.isNull()) - cutShape = cutShape.makeTransform(trsf); + if (!fuseShape.isNull()) { + fuseShape = fuseShape.makeTransform(trsf); + } + if (!cutShape.isNull()) { + cutShape = cutShape.makeTransform(trsf); + } #endif - } - else { - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Only additive and subtractive features can be transformed")); - } - TopoDS_Shape current = support; - if (!fuseShape.isNull()) { - TopTools_ListOfShape shapeArguments; - shapeArguments.Append(current); - TopTools_ListOfShape shapeTools = getTransformedCompShape(fuseShape.getShape()); - if (!shapeTools.IsEmpty()) { - std::unique_ptr mkBool(new BRepAlgoAPI_Fuse()); - mkBool->SetArguments(shapeArguments); - mkBool->SetTools(shapeTools); - mkBool->Build(); - if (!mkBool->IsDone()) { - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Boolean operation failed")); + TopoDS_Shape current = support; + if (!fuseShape.isNull()) { + TopTools_ListOfShape shapeArguments; + shapeArguments.Append(current); + TopTools_ListOfShape shapeTools = getTransformedCompShape(fuseShape.getShape()); + if (!shapeTools.IsEmpty()) { + BRepAlgoAPI_Fuse mkBool; + mkBool.SetArguments(shapeArguments); + mkBool.SetTools(shapeTools); + mkBool.Build(); + if (!mkBool.IsDone()) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Boolean operation failed")); + } + current = mkBool.Shape(); + } } - current = mkBool->Shape(); - } - } - if (!cutShape.isNull()) { - TopTools_ListOfShape shapeArguments; - shapeArguments.Append(current); - TopTools_ListOfShape shapeTools = getTransformedCompShape(cutShape.getShape()); - if (!shapeTools.IsEmpty()) { - std::unique_ptr mkBool(new BRepAlgoAPI_Cut()); - mkBool->SetArguments(shapeArguments); - mkBool->SetTools(shapeTools); - mkBool->Build(); - if (!mkBool->IsDone()) { - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Boolean operation failed")); + if (!cutShape.isNull()) { + TopTools_ListOfShape shapeArguments; + shapeArguments.Append(current); + TopTools_ListOfShape shapeTools = getTransformedCompShape(cutShape.getShape()); + if (!shapeTools.IsEmpty()) { + BRepAlgoAPI_Cut mkBool; + mkBool.SetArguments(shapeArguments); + mkBool.SetTools(shapeTools); + mkBool.Build(); + if (!mkBool.IsDone()) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Boolean operation failed")); + } + current = mkBool.Shape(); + } } - current = mkBool->Shape(); - } - } - support = current; // Use result of this operation for fuse/cut of next original + support = current; // Use result of this operation for fuse/cut of next original + } + break; + case Mode::TransformBody: { + TopTools_ListOfShape shapeArguments; + shapeArguments.Append(support); + TopTools_ListOfShape shapeTools = getTransformedCompShape(support); + if (!shapeTools.IsEmpty()) { + BRepAlgoAPI_Fuse mkBool; + mkBool.SetArguments(shapeArguments); + mkBool.SetTools(shapeTools); + mkBool.Build(); + if (!mkBool.IsDone()) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Boolean operation failed")); + } + support = mkBool.Shape(); + } + break; + } } support = refineShapeIfActive(support); @@ -356,78 +450,24 @@ TopoDS_Shape Transformed::refineShapeIfActive(const TopoDS_Shape& oldShape) cons return oldShape; } -void Transformed::divideTools(const std::vector &toolsIn, std::vector &individualsOut, - TopoDS_Compound &compoundOut) const -{ - using ShapeBoundPair = std::pair; - using PairList = std::list; - using PairVector = std::vector; - - PairList pairList; - - std::vector::const_iterator it; - for (it = toolsIn.begin(); it != toolsIn.end(); ++it) { - Bnd_Box bound; - BRepBndLib::Add(*it, bound); - bound.SetGap(0.0); - ShapeBoundPair temp = std::make_pair(*it, bound); - pairList.push_back(temp); - } - - BRep_Builder builder; - builder.MakeCompound(compoundOut); - - while(!pairList.empty()) { - PairVector currentGroup; - currentGroup.push_back(pairList.front()); - pairList.pop_front(); - PairList::iterator it = pairList.begin(); - while(it != pairList.end()) { - PairVector::const_iterator groupIt; - bool found(false); - for (groupIt = currentGroup.begin(); groupIt != currentGroup.end(); ++groupIt) { - if (!(*it).second.IsOut((*groupIt).second)) {//touching means is out. - found = true; - break; - } - } - if (found) { - currentGroup.push_back(*it); - pairList.erase(it); - it=pairList.begin(); - continue; - } - ++it; - } - - if (currentGroup.size() == 1) { - builder.Add(compoundOut, currentGroup.front().first); - } - else { - PairVector::const_iterator groupIt; - for (groupIt = currentGroup.begin(); groupIt != currentGroup.end(); ++groupIt) - individualsOut.push_back((*groupIt).first); - } - } -} - TopoDS_Shape Transformed::getRemainingSolids(const TopoDS_Shape& shape) { BRep_Builder builder; TopoDS_Compound compShape; builder.MakeCompound(compShape); - if (shape.IsNull()) + if (shape.IsNull()) { Standard_Failure::Raise("Shape is null"); + } TopExp_Explorer xp; - xp.Init(shape,TopAbs_SOLID); + xp.Init(shape, TopAbs_SOLID); xp.Next(); // skip the first for (; xp.More(); xp.Next()) { builder.Add(compShape, xp.Current()); } - return TopoDS_Shape(std::move(compShape)); + return {std::move(compShape)}; } -} +} // namespace PartDesign diff --git a/src/Mod/PartDesign/App/FeatureTransformed.h b/src/Mod/PartDesign/App/FeatureTransformed.h index 3e8e50104a..18c93e8194 100644 --- a/src/Mod/PartDesign/App/FeatureTransformed.h +++ b/src/Mod/PartDesign/App/FeatureTransformed.h @@ -37,18 +37,25 @@ namespace PartDesign * Abstract superclass of all features that are created by transformation of another feature * Transformations are translation, rotation and mirroring */ -class PartDesignExport Transformed : public PartDesign::Feature +class PartDesignExport Transformed: public PartDesign::Feature { PROPERTY_HEADER_WITH_OVERRIDE(PartDesign::Transformed); public: + enum class Mode + { + TransformToolShapes, + TransformBody + }; + Transformed(); - /** The shapes to be transformed - * if Originals is empty the instance is just a container for storing transformation data - */ + /** The features to be transformed + */ App::PropertyLinkList Originals; + App::PropertyEnumeration TransformMode; + App::PropertyBool Refine; /** @@ -58,48 +65,54 @@ public: * silently return a nullptr, otherwise throw Base::Exception. * Default is false. */ - Part::Feature* getBaseObject(bool silent=false) const override; + Part::Feature* getBaseObject(bool silent = false) const override; /// Return the sketch of the first original App::DocumentObject* getSketchObject() const; + /// Return true if this feature is a child of a MultiTransform + bool isMultiTransformChild() const; + /// Get the list of transformations describing the members of the pattern // Note: Only the Scaled feature requires the originals - virtual const std::list getTransformations(const std::vector /*originals*/) { - return std::list(); // Default method + virtual const std::list + getTransformations(const std::vector /*originals*/) + { + return std::list(); // Default method } - /** @name methods override feature */ + /** @name methods override feature */ //@{ /** Recalculate the feature - * Gets the transformations from the virtual getTransformations() method of the sub class - * and applies them to every member of Originals. The total number of copies including - * the untransformed Originals will be sizeof(Originals) times sizeof(getTransformations()) - * If Originals is empty, execute() returns immediately without doing anything as - * the actual processing will happen in the MultiTransform feature - */ - App::DocumentObjectExecReturn *execute() override; + * Gets the transformations from the virtual getTransformations() method of the sub class + * and applies them to every member of Originals. The total number of copies including + * the untransformed Originals will be sizeof(Originals) times sizeof(getTransformations()) + * If Originals is empty, execute() returns immediately without doing anything as + * the actual processing will happen in the MultiTransform feature + */ + App::DocumentObjectExecReturn* execute() override; short mustExecute() const override; //@} /** returns the compound of the shapes that were rejected during the last execute - * because they did not overlap with the support - */ + * because they did not overlap with the support + */ TopoDS_Shape rejected; protected: - void Restore(Base::XMLReader &reader) override; - void handleChangedPropertyType(Base::XMLReader &reader, const char * TypeName, App::Property * prop) override; + void Restore(Base::XMLReader& reader) override; + void handleChangedPropertyType(Base::XMLReader& reader, + const char* TypeName, + App::Property* prop) override; + virtual void positionBySupport(); TopoDS_Shape refineShapeIfActive(const TopoDS_Shape&) const; - void divideTools(const std::vector &toolsIn, std::vector &individualsOut, - TopoDS_Compound &compoundOut) const; static TopoDS_Shape getRemainingSolids(const TopoDS_Shape&); private: }; -} //namespace PartDesign +} // namespace PartDesign -#endif // PARTDESIGN_FeatureTransformed_H +#endif // PARTDESIGN_FeatureTransformed_H diff --git a/src/Mod/PartDesign/Gui/Command.cpp b/src/Mod/PartDesign/Gui/Command.cpp index 017dd46d41..1c6170a935 100644 --- a/src/Mod/PartDesign/Gui/Command.cpp +++ b/src/Mod/PartDesign/Gui/Command.cpp @@ -1981,22 +1981,7 @@ void prepareTransformed(PartDesign::Body *pcActiveBody, Gui::Command* cmd, const { std::string FeatName = cmd->getUniqueObjectName(which.c_str(), pcActiveBody); - auto accepter = [=](std::vector features) -> bool { - - if (features.empty()) - return false; - - return true; - }; - auto worker = [=](std::vector features) { - std::stringstream str; - str << cmd->getObjectCmd(FeatName.c_str(), pcActiveBody->getDocument()) << ".Originals = ["; - for (auto feature : features) { - str << cmd->getObjectCmd(feature) << ","; - } - str << "]"; - std::string msg("Make "); msg += which; Gui::Command::openCommand(msg.c_str()); @@ -2004,10 +1989,21 @@ void prepareTransformed(PartDesign::Body *pcActiveBody, Gui::Command* cmd, const // FIXME: There seems to be kind of a race condition here, leading to sporadic errors like // Exception (Thu Sep 6 11:52:01 2012): 'App.Document' object has no attribute 'Mirrored' Gui::Command::updateActive(); // Helps to ensure that the object already exists when the next command comes up - Gui::Command::doCommand(Gui::Command::Doc, str.str().c_str()); auto Feat = pcActiveBody->getDocument()->getObject(FeatName.c_str()); + if (features.empty()) { + FCMD_OBJ_CMD(Feat, "TransformMode = \"Transform body\""); + } else { + std::stringstream str; + str << "Originals = ["; + for (auto feature : features) { + str << cmd->getObjectCmd(feature) << ","; + } + str << "]"; + FCMD_OBJ_CMD(Feat, str.str().c_str()); + } + // TODO What is this function supposed to do? (2015-08-05, Fat-Zer) func(Feat, features); @@ -2017,44 +2013,7 @@ void prepareTransformed(PartDesign::Body *pcActiveBody, Gui::Command* cmd, const }; // Get a valid original from the user - // First check selections std::vector features = cmd->getSelection().getObjectsOfType(PartDesign::Feature::getClassTypeId()); - // Next create a list of all eligible objects - if (features.empty()) { - features = cmd->getDocument()->getObjectsOfType(PartDesign::Feature::getClassTypeId()); - // If there is more than one selected or eligible object, show dialog and let user pick one - if (features.size() > 1) { - std::vector status; - for (unsigned i = 0; i < features.size(); i++) - status.push_back(PartDesignGui::TaskFeaturePick::validFeature); - - Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(); - PartDesignGui::TaskDlgFeaturePick* pickDlg = qobject_cast(dlg); - if (dlg && !pickDlg) { - QMessageBox msgBox; - msgBox.setText(QObject::tr("A dialog is already open in the task panel")); - msgBox.setInformativeText(QObject::tr("Do you want to close this dialog?")); - msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - msgBox.setDefaultButton(QMessageBox::Yes); - int ret = msgBox.exec(); - if (ret == QMessageBox::Yes) - Gui::Control().closeDialog(); - else - return; - } - - if (dlg) - Gui::Control().closeDialog(); - - Gui::Selection().clearSelection(); - Gui::Control().showDialog(new PartDesignGui::TaskDlgFeaturePick(features, status, accepter, worker, false)); - return; - } else if (features.empty()) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("No valid features in this document"), - QObject::tr("Please create a feature first.")); - return; - } - } PartDesign::Body* activeBody = PartDesignGui::getBody(true); for (auto feature : features) { @@ -2103,24 +2062,17 @@ void CmdPartDesignMirrored::activated(int iMsg) return; Gui::Command* cmd = this; - auto worker = [cmd](App::DocumentObject *Feat, std::vector features) { - - if (features.empty()) - return; - + auto worker = [cmd, pcActiveBody](App::DocumentObject *Feat, std::vector features) { bool direction = false; - if (features.front()->isDerivedFrom(PartDesign::ProfileBased::getClassTypeId())) { - Part::Part2DObject *sketch = (static_cast(features.front()))->getVerifiedSketch(/* silent =*/ true); + if (!features.empty() && features.front()->isDerivedFrom(PartDesign::ProfileBased::getClassTypeId())) { + Part::Part2DObject* sketch = (static_cast(features.front()))->getVerifiedSketch(/* silent =*/ true); if (sketch) { FCMD_OBJ_CMD(Feat,"MirrorPlane = ("<(Part::BodyBase::findBodyOf(features.front())); - if (body) { - FCMD_OBJ_CMD(Feat,"MirrorPlane = ("<getOrigin()->getXY())<<", [''])"); - } + FCMD_OBJ_CMD(Feat,"MirrorPlane = ("<getOrigin()->getXY())<<", [''])"); } finishTransformed(cmd, Feat); @@ -2165,13 +2117,9 @@ void CmdPartDesignLinearPattern::activated(int iMsg) return; Gui::Command* cmd = this; - auto worker = [cmd](App::DocumentObject *Feat, std::vector features) { - - if (!Feat || features.empty()) - return; - + auto worker = [cmd, pcActiveBody](App::DocumentObject *Feat, std::vector features) { bool direction = false; - if (features.front()->isDerivedFrom(PartDesign::ProfileBased::getClassTypeId())) { + if (!features.empty() && features.front()->isDerivedFrom(PartDesign::ProfileBased::getClassTypeId())) { Part::Part2DObject *sketch = (static_cast(features.front()))->getVerifiedSketch(/* silent =*/ true); if (sketch) { FCMD_OBJ_CMD(Feat,"Direction = ("<(Part::BodyBase::findBodyOf(features.front())); - if (body) { - FCMD_OBJ_CMD(Feat,"Direction = ("<getOrigin()->getX())<<",[''])"); - } + FCMD_OBJ_CMD(Feat,"Direction = ("<getOrigin()->getX())<<",[''])"); } FCMD_OBJ_CMD(Feat,"Length = 100"); FCMD_OBJ_CMD(Feat,"Occurrences = 2"); @@ -2229,13 +2174,10 @@ void CmdPartDesignPolarPattern::activated(int iMsg) return; Gui::Command* cmd = this; - auto worker = [cmd](App::DocumentObject *Feat, std::vector features) { - - if (!Feat || features.empty()) - return; + auto worker = [cmd, pcActiveBody](App::DocumentObject *Feat, std::vector features) { bool direction = false; - if (features.front()->isDerivedFrom(PartDesign::ProfileBased::getClassTypeId())) { + if (!features.empty() && features.front()->isDerivedFrom(PartDesign::ProfileBased::getClassTypeId())) { Part::Part2DObject *sketch = (static_cast(features.front()))->getVerifiedSketch(/* silent =*/ true); if (sketch) { FCMD_OBJ_CMD(Feat,"Axis = ("<(Part::BodyBase::findBodyOf(features.front())); - if (body) { - FCMD_OBJ_CMD(Feat,"Axis = ("<getOrigin()->getZ())<<",[''])"); - } + FCMD_OBJ_CMD(Feat,"Axis = ("<getOrigin()->getZ())<<",[''])"); } FCMD_OBJ_CMD(Feat,"Angle = 360"); @@ -2293,11 +2232,7 @@ void CmdPartDesignScaled::activated(int iMsg) return; Gui::Command* cmd = this; - auto worker = [cmd](App::DocumentObject *Feat, std::vector features) { - - if (!Feat || features.empty()) - return; - + auto worker = [cmd](App::DocumentObject *Feat, std::vector /*features*/) { FCMD_OBJ_CMD(Feat,"Factor = 2"); FCMD_OBJ_CMD(Feat,"Occurrences = 2"); @@ -2411,11 +2346,7 @@ void CmdPartDesignMultiTransform::activated(int iMsg) } else { Gui::Command* cmd = this; - auto worker = [cmd, pcActiveBody](App::DocumentObject *Feat, std::vector features) { - - if (!Feat || features.empty()) - return; - + auto worker = [cmd, pcActiveBody](App::DocumentObject *Feat, std::vector /*features*/) { // Make sure the user isn't presented with an empty screen because no transformations are defined yet... App::DocumentObject* prevSolid = pcActiveBody->Tip.getValue(); if (prevSolid) { diff --git a/src/Mod/PartDesign/Gui/TaskTransformedParameters.cpp b/src/Mod/PartDesign/Gui/TaskTransformedParameters.cpp index d2045b6b57..e014e5d614 100644 --- a/src/Mod/PartDesign/Gui/TaskTransformedParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskTransformedParameters.cpp @@ -120,8 +120,33 @@ void TaskTransformedParameters::setupUI() // Get the feature data auto pcTransformed = static_cast(getObject()); - std::vector originals = pcTransformed->Originals.getValues(); + using Mode = PartDesign::Transformed::Mode; + + ui->buttonGroupMode->setId(ui->radioTransformBody, static_cast(Mode::TransformBody)); + ui->buttonGroupMode->setId(ui->radioTransformToolShapes, static_cast(Mode::TransformToolShapes)); + + connect(ui->buttonGroupMode, +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) + qOverload(&QButtonGroup::buttonClicked), +#else + &QButtonGroup::idClicked, +#endif + this, + &TaskTransformedParameters::onModeChanged); + + auto const mode = static_cast(pcTransformed->TransformMode.getValue()); + ui->groupFeatureList->setEnabled(mode == Mode::TransformToolShapes); + switch (mode) { + case Mode::TransformBody: + ui->radioTransformBody->setChecked(true); + break; + case Mode::TransformToolShapes: + ui->radioTransformToolShapes->setChecked(true); + break; + } + + std::vector originals = pcTransformed->Originals.getValues(); // Fill data into dialog elements for (auto obj : originals) { if (obj) { @@ -272,6 +297,26 @@ bool TaskTransformedParameters::isEnabledTransaction() const return enableTransaction; } +void TaskTransformedParameters::onModeChanged(int mode_id) +{ + if (mode_id < 0) { + return; + } + + auto pcTransformed = static_cast(getObject()); + pcTransformed->TransformMode.setValue(mode_id); + + using Mode = PartDesign::Transformed::Mode; + Mode const mode = static_cast(mode_id); + + ui->groupFeatureList->setEnabled(mode == Mode::TransformToolShapes); + if (mode == Mode::TransformBody) { + ui->listWidgetFeatures->clear(); + } + setupTransaction(); + recomputeFeature(); +} + void TaskTransformedParameters::onButtonAddFeature(bool checked) { if (checked) { diff --git a/src/Mod/PartDesign/Gui/TaskTransformedParameters.h b/src/Mod/PartDesign/Gui/TaskTransformedParameters.h index a94c30add5..98ebd66498 100644 --- a/src/Mod/PartDesign/Gui/TaskTransformedParameters.h +++ b/src/Mod/PartDesign/Gui/TaskTransformedParameters.h @@ -228,6 +228,7 @@ private Q_SLOTS: void onButtonRemoveFeature(bool checked); void onFeatureDeleted(); void indexesMoved(); + void onModeChanged(int mode_id); private: /** Setup the parameter UI. diff --git a/src/Mod/PartDesign/Gui/TaskTransformedParameters.ui b/src/Mod/PartDesign/Gui/TaskTransformedParameters.ui index 14bfe88d8a..0535e4cc3f 100644 --- a/src/Mod/PartDesign/Gui/TaskTransformedParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskTransformedParameters.ui @@ -6,8 +6,8 @@ 0 0 - 262 - 207 + 297 + 248 @@ -15,43 +15,102 @@ - - - - - Add feature - - - true - - - - - - - Remove feature - - - true - - - - + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Transform body + + + true + + + buttonGroupMode + + + + + + + Transform tool shapes + + + buttonGroupMode + + + + + - - - - 16777215 - 120 - - - - List can be reordered by dragging - - - QAbstractItemView::InternalMove - + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + Add feature + + + true + + + + + + + Remove feature + + + true + + + + + + + + + + 16777215 + 120 + + + + List can be reordered by dragging + + + QAbstractItemView::InternalMove + + + + @@ -70,6 +129,8 @@ + radioTransformBody + radioTransformToolShapes buttonAddFeature buttonRemoveFeature listWidgetFeatures @@ -77,4 +138,7 @@ + + +