From 82c3e107d745661e686b1fb6ec242742cff03e53 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Sun, 7 Apr 2024 11:47:13 -0400 Subject: [PATCH 01/24] TopoShape/Part: Bring in PartDesign dependencies --- src/Mod/Part/App/PartFeature.cpp | 41 ++ src/Mod/Part/App/PartFeature.h | 4 + src/Mod/Part/App/TopoShape.h | 54 ++- src/Mod/Part/App/TopoShapeExpansion.cpp | 371 +++++++++--------- src/Mod/PartDesign/App/Feature.cpp | 39 ++ src/Mod/PartDesign/App/Feature.h | 2 + src/Mod/PartDesign/App/FeatureAddSub.cpp | 10 + src/Mod/PartDesign/App/FeatureAddSub.h | 1 + src/Mod/PartDesign/App/FeatureExtrude.cpp | 327 +++++++++++++++ src/Mod/PartDesign/App/FeatureExtrude.h | 23 ++ src/Mod/PartDesign/App/FeatureSketchBased.cpp | 242 ++++++++++++ src/Mod/PartDesign/App/FeatureSketchBased.h | 37 +- 12 files changed, 935 insertions(+), 216 deletions(-) diff --git a/src/Mod/Part/App/PartFeature.cpp b/src/Mod/Part/App/PartFeature.cpp index a359982cb0..e569305e36 100644 --- a/src/Mod/Part/App/PartFeature.cpp +++ b/src/Mod/Part/App/PartFeature.cpp @@ -1359,6 +1359,47 @@ std::vector Part::findAllFacesCutBy( return result; } +std::vector Part::findAllFacesCutBy( + const TopoShape& shape, const TopoShape& face, const gp_Dir& dir) +{ + // Find the centre of gravity of the face + GProp_GProps props; + BRepGProp::SurfaceProperties(face.getShape(),props); + gp_Pnt cog = props.CentreOfMass(); + + // create a line through the centre of gravity + gp_Lin line = gce_MakeLin(cog, dir); + + // Find intersection of line with all faces of the shape + std::vector result; + BRepIntCurveSurface_Inter mkSection; + // TODO: Less precision than Confusion() should be OK? + + for (mkSection.Init(shape.getShape(), line, Precision::Confusion()); mkSection.More(); mkSection.Next()) { + gp_Pnt iPnt = mkSection.Pnt(); + double dsq = cog.SquareDistance(iPnt); + + if (dsq < Precision::Confusion()) + continue; // intersection with original face + + // Find out which side of the original face the intersection is on + gce_MakeDir mkDir(cog, iPnt); + if (!mkDir.IsDone()) + continue; // some error (appears highly unlikely to happen, though...) + + if (mkDir.Value().IsOpposite(dir, Precision::Confusion())) + continue; // wrong side of face (opposite to extrusion direction) + + cutFaces newF; + newF.face = mkSection.Face(); + newF.face.mapSubElement(shape); + newF.distsq = dsq; + result.push_back(newF); + } + + return result; +} + bool Part::checkIntersection(const TopoDS_Shape& first, const TopoDS_Shape& second, const bool quick, const bool touch_is_intersection) { diff --git a/src/Mod/Part/App/PartFeature.h b/src/Mod/Part/App/PartFeature.h index 56a482d57a..54bd009a69 100644 --- a/src/Mod/Part/App/PartFeature.h +++ b/src/Mod/Part/App/PartFeature.h @@ -220,6 +220,10 @@ PartExport std::vector findAllFacesCutBy(const TopoDS_Shape& shape, const TopoDS_Shape& face, const gp_Dir& dir); +PartExport + std::vector findAllFacesCutBy(const TopoShape& shape, + const TopoShape& face, const gp_Dir& dir); + /** * Check for intersection between the two shapes. Only solids are guaranteed to work properly * There are two modes: diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index a7f0ebe048..74db8e5697 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -1153,16 +1153,14 @@ public: * a self reference so that multiple operations can be carried out * for the same shape in the same line of code. */ - // TODO: This code was transferred in Feb 2024 as part of the toponaming project, but appears to be - // unused. It is potentially useful if debugged. -// TopoShape &makeElementPrismUntil(const TopoShape &base, -// const TopoShape& profile, -// const TopoShape& supportFace, -// const TopoShape& upToFace, -// const gp_Dir& direction, -// PrismMode mode, -// Standard_Boolean checkLimits = Standard_True, -// const char *op=nullptr); + TopoShape &makeElementPrismUntil(const TopoShape &base, + const TopoShape& profile, + const TopoShape& supportFace, + const TopoShape& upToFace, + const gp_Dir& direction, + PrismMode mode, + Standard_Boolean checkLimits = Standard_True, + const char *op=nullptr); /** Make a prism based on this shape that is either depression or protrusion of a profile shape up to a given face * @@ -1181,25 +1179,23 @@ public: * * @return Return the generated new shape. The TopoShape itself is not modified. */ - // TODO: This code was transferred in Feb 2024 as part of the toponaming project, but appears to be - // unused. It is potentially useful if debugged. -// TopoShape makeElementPrismUntil(const TopoShape& profile, -// const TopoShape& supportFace, -// const TopoShape& upToFace, -// const gp_Dir& direction, -// PrismMode mode, -// Standard_Boolean checkLimits = Standard_True, -// const char *op=nullptr) const -// { -// return TopoShape(0,Hasher).makeElementPrismUntil(*this, -// profile, -// supportFace, -// upToFace, -// direction, -// mode, -// checkLimits, -// op); -// } + TopoShape makeElementPrismUntil(const TopoShape& profile, + const TopoShape& supportFace, + const TopoShape& upToFace, + const gp_Dir& direction, + PrismMode mode, + Standard_Boolean checkLimits = Standard_True, + const char *op=nullptr) const + { + return TopoShape(0,Hasher).makeElementPrismUntil(*this, + profile, + supportFace, + upToFace, + direction, + mode, + checkLimits, + op); + } /* Make a shell or solid by sweeping profile wire along a spine diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index cce20f8e4a..ad69144905 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -4231,192 +4231,191 @@ TopoShape& TopoShape::makeElementPrism(const TopoShape& base, const gp_Vec& vec, return makeElementShape(mkPrism, base, op); } -// TODO: This code was transferred in Feb 2024 as part of the toponaming project, but appears to be -// unused. It is potentially useful if debugged. -// TopoShape& TopoShape::makeElementPrismUntil(const TopoShape& _base, -// const TopoShape& profile, -// const TopoShape& supportFace, -// const TopoShape& __uptoface, -// const gp_Dir& direction, -// PrismMode Mode, -// Standard_Boolean checkLimits, -// const char* op) -//{ -// if (!op) { -// op = Part::OpCodes::Prism; -// } -// -// BRepFeat_MakePrism PrismMaker; -// -// TopoShape _uptoface(__uptoface); -// if (checkLimits && _uptoface.shapeType(true) == TopAbs_FACE -// && !BRep_Tool::NaturalRestriction(TopoDS::Face(_uptoface.getShape()))) { -// // When using the face with BRepFeat_MakePrism::Perform(const TopoDS_Shape& Until) -// // then the algorithm expects that the 'NaturalRestriction' flag is set in order -// // to work as expected. -// BRep_Builder builder; -// _uptoface = _uptoface.makeElementCopy(); -// builder.NaturalRestriction(TopoDS::Face(_uptoface.getShape()), Standard_True); -// } -// -// TopoShape uptoface(_uptoface); -// TopoShape base(_base); -// -// if (base.isNull()) { -// Mode = PrismMode::None; -// base = profile; -// } -// -// // Check whether the face has limits or not. Unlimited faces have no wire -// // Note: Datum planes are always unlimited -// if (checkLimits && uptoface.hasSubShape(TopAbs_WIRE)) { -// TopoDS_Face face = TopoDS::Face(uptoface.getShape()); -// bool remove_limits = false; -// // Remove the limits of the upToFace so that the extrusion works even if profile is larger -// // than the upToFace -// for (auto& sketchface : profile.getSubTopoShapes(TopAbs_FACE)) { -// // Get outermost wire of sketch face -// TopoShape outerWire = sketchface.splitWires(); -// BRepProj_Projection proj(TopoDS::Wire(outerWire.getShape()), face, direction); -// if (!proj.More() || !proj.Current().Closed()) { -// remove_limits = true; -// break; -// } -// } -// -// // It must also be checked that all projected inner wires of the upToFace -// // lie outside the sketch shape. If this is not the case then the sketch -// // shape is not completely covered by the upToFace. See #0003141 -// if (!remove_limits) { -// std::vector wires; -// uptoface.splitWires(&wires); -// for (auto& w : wires) { -// BRepProj_Projection proj(TopoDS::Wire(w.getShape()), -// profile.getShape(), -// -direction); -// if (proj.More()) { -// remove_limits = true; -// break; -// } -// } -// } -// -// if (remove_limits) { -// // Note: Using an unlimited face every time gives unnecessary failures for concave -// faces TopLoc_Location loc = face.Location(); BRepAdaptor_Surface adapt(face, -// Standard_False); -// // use the placement of the adapter, not of the upToFace -// loc = TopLoc_Location(adapt.Trsf()); -// BRepBuilderAPI_MakeFace mkFace(adapt.Surface().Surface(), Precision::Confusion()); -// if (!mkFace.IsDone()) { -// remove_limits = false; -// } -// else { -// uptoface.setShape(located(mkFace.Shape(), loc), false); -// } -// } -// } -// -// TopoShape uptofaceCopy = uptoface; -// bool checkBase = false; -// auto retry = [&]() { -// if (!uptoface.isSame(_uptoface)) { -// // retry using the original up to face in case unnecessary failure -// // due to removing the limits -// uptoface = _uptoface; -// return true; -// } -// if ((!_base.isNull() && base.isSame(_base)) || (_base.isNull() && base.isSame(profile))) { -// // It is unclear under exactly what condition extrude up to face -// // can fail. Either the support face or the up to face must be part -// // of the base, or maybe some thing else. -// // -// // To deal with it, we retry again by disregard the supplied base, -// // and use up to face to extrude our own base. Later on, use the -// // supplied base (i.e. _base) to calculate the final shape if the -// // mode is FuseWithBase or CutWithBase. -// checkBase = true; -// uptoface = uptofaceCopy; -// base.makeElementPrism(_uptoface, direction); -// return true; -// } -// return false; -// }; -// -// std::vector srcShapes; -// TopoShape result; -// for (;;) { -// try { -// result = base; -// -// // We do not rely on BRepFeat_MakePrism to perform fuse or cut for -// // us because of its poor support of shape history. -// auto mode = PrismMode::None; -// -// for (auto& face : profile.getSubTopoShapes( -// profile.hasSubShape(TopAbs_FACE) ? TopAbs_FACE : TopAbs_WIRE)) { -// srcShapes.clear(); -// if (!profile.isNull() && !result.findShape(profile.getShape())) { -// srcShapes.push_back(profile); -// } -// if (!supportFace.isNull() && !result.findShape(supportFace.getShape())) { -// srcShapes.push_back(supportFace); -// } -// -// // DO NOT include uptoface for element mapping. Because OCCT -// // BRepFeat_MakePrism will report all top extruded face being -// // modified by the uptoface. If there are more than one face in -// // the profile, this will cause unnecessary duplicated element -// // mapped name. And will also disrupte element history tracing -// // back to the profile sketch. -// // -// // if (!uptoface.isNull() && !this->findShape(uptoface.getShape())) -// // srcShapes.push_back(uptoface); -// -// srcShapes.push_back(result); -// -// PrismMaker.Init(result.getShape(), -// face.getShape(), -// TopoDS::Face(supportFace.getShape()), -// direction, -// mode, -// Standard_False); -// mode = PrismMode::FuseWithBase; -// -// PrismMaker.Perform(uptoface.getShape()); -// -// if (!PrismMaker.IsDone() || PrismMaker.Shape().IsNull()) { -// FC_THROWM(Base::CADKernelError, "BRepFeat_MakePrism: extrusion failed"); -// } -// -// result.makeElementShape(PrismMaker, srcShapes, uptoface, op); -// } -// break; -// } -// catch (Base::Exception&) { -// if (!retry()) { -// throw; -// } -// } -// catch (Standard_Failure&) { -// if (!retry()) { -// throw; -// } -// } -// } -// -// if (!_base.isNull() && Mode != PrismMode::None) { -// if (Mode == PrismMode::FuseWithBase) { -// result.makeElementFuse({_base, result}); -// } -// else { -// result.makeElementCut({_base, result}); -// } -// } -// -// *this = result; -// return *this; -//} + TopoShape& TopoShape::makeElementPrismUntil(const TopoShape& _base, + const TopoShape& profile, + const TopoShape& supportFace, + const TopoShape& __uptoface, + const gp_Dir& direction, + PrismMode Mode, + Standard_Boolean checkLimits, + const char* op) +{ + if (!op) { + op = Part::OpCodes::Prism; + } + + BRepFeat_MakePrism PrismMaker; + + TopoShape _uptoface(__uptoface); + if (checkLimits && _uptoface.shapeType(true) == TopAbs_FACE + && !BRep_Tool::NaturalRestriction(TopoDS::Face(_uptoface.getShape()))) { + // When using the face with BRepFeat_MakePrism::Perform(const TopoDS_Shape& Until) + // then the algorithm expects that the 'NaturalRestriction' flag is set in order + // to work as expected. + BRep_Builder builder; + _uptoface = _uptoface.makeElementCopy(); + builder.NaturalRestriction(TopoDS::Face(_uptoface.getShape()), Standard_True); + } + + TopoShape uptoface(_uptoface); + TopoShape base(_base); + + if (base.isNull()) { + Mode = PrismMode::None; + base = profile; + } + + // Check whether the face has limits or not. Unlimited faces have no wire + // Note: Datum planes are always unlimited + if (checkLimits && uptoface.hasSubShape(TopAbs_WIRE)) { + TopoDS_Face face = TopoDS::Face(uptoface.getShape()); + bool remove_limits = false; + // Remove the limits of the upToFace so that the extrusion works even if profile is larger + // than the upToFace + for (auto& sketchface : profile.getSubTopoShapes(TopAbs_FACE)) { + // Get outermost wire of sketch face + TopoShape outerWire = sketchface.splitWires(); + BRepProj_Projection proj(TopoDS::Wire(outerWire.getShape()), face, direction); + if (!proj.More() || !proj.Current().Closed()) { + remove_limits = true; + break; + } + } + + // It must also be checked that all projected inner wires of the upToFace + // lie outside the sketch shape. If this is not the case then the sketch + // shape is not completely covered by the upToFace. See #0003141 + if (!remove_limits) { + std::vector wires; + uptoface.splitWires(&wires); + for (auto& w : wires) { + BRepProj_Projection proj(TopoDS::Wire(w.getShape()), + profile.getShape(), + -direction); + if (proj.More()) { + remove_limits = true; + break; + } + } + } + + if (remove_limits) { + // Note: Using an unlimited face every time gives unnecessary failures for concave + // faces + TopLoc_Location loc = face.Location(); BRepAdaptor_Surface adapt(face, + Standard_False); + // use the placement of the adapter, not of the upToFace + loc = TopLoc_Location(adapt.Trsf()); + BRepBuilderAPI_MakeFace mkFace(adapt.Surface().Surface(), Precision::Confusion()); + if (!mkFace.IsDone()) { + remove_limits = false; + } + else { + uptoface.setShape(located(mkFace.Shape(), loc), false); + } + } + } + + TopoShape uptofaceCopy = uptoface; + bool checkBase = false; + auto retry = [&]() { + if (!uptoface.isSame(_uptoface)) { + // retry using the original up to face in case unnecessary failure + // due to removing the limits + uptoface = _uptoface; + return true; + } + if ((!_base.isNull() && base.isSame(_base)) || (_base.isNull() && base.isSame(profile))) { + // It is unclear under exactly what condition extrude up to face + // can fail. Either the support face or the up to face must be part + // of the base, or maybe some thing else. + // + // To deal with it, we retry again by disregard the supplied base, + // and use up to face to extrude our own base. Later on, use the + // supplied base (i.e. _base) to calculate the final shape if the + // mode is FuseWithBase or CutWithBase. + checkBase = true; + uptoface = uptofaceCopy; + base.makeElementPrism(_uptoface, direction); + return true; + } + return false; + }; + + std::vector srcShapes; + TopoShape result; + for (;;) { + try { + result = base; + + // We do not rely on BRepFeat_MakePrism to perform fuse or cut for + // us because of its poor support of shape history. + auto mode = PrismMode::None; + + for (auto& face : profile.getSubTopoShapes( + profile.hasSubShape(TopAbs_FACE) ? TopAbs_FACE : TopAbs_WIRE)) { + srcShapes.clear(); + if (!profile.isNull() && !result.findShape(profile.getShape())) { + srcShapes.push_back(profile); + } + if (!supportFace.isNull() && !result.findShape(supportFace.getShape())) { + srcShapes.push_back(supportFace); + } + + // DO NOT include uptoface for element mapping. Because OCCT + // BRepFeat_MakePrism will report all top extruded face being + // modified by the uptoface. If there are more than one face in + // the profile, this will cause unnecessary duplicated element + // mapped name. And will also disrupte element history tracing + // back to the profile sketch. + // + // if (!uptoface.isNull() && !this->findShape(uptoface.getShape())) + // srcShapes.push_back(uptoface); + + srcShapes.push_back(result); + + PrismMaker.Init(result.getShape(), + face.getShape(), + TopoDS::Face(supportFace.getShape()), + direction, + mode, + Standard_False); + mode = PrismMode::FuseWithBase; + + PrismMaker.Perform(uptoface.getShape()); + + if (!PrismMaker.IsDone() || PrismMaker.Shape().IsNull()) { + FC_THROWM(Base::CADKernelError, "BRepFeat_MakePrism: extrusion failed"); + } + + result.makeElementShape(PrismMaker, srcShapes, uptoface, op); + } + break; + } + catch (Base::Exception&) { + if (!retry()) { + throw; + } + } + catch (Standard_Failure&) { + if (!retry()) { + throw; + } + } + } + + if (!_base.isNull() && Mode != PrismMode::None) { + if (Mode == PrismMode::FuseWithBase) { + result.makeElementFuse({_base, result}); + } + else { + result.makeElementCut({_base, result}); + } + } + + *this = result; + return *this; +} TopoShape& TopoShape::makeElementRevolve(const TopoShape& _base, const gp_Ax1& axis, diff --git a/src/Mod/PartDesign/App/Feature.cpp b/src/Mod/PartDesign/App/Feature.cpp index d6e9a31dc8..f3fe0df1aa 100644 --- a/src/Mod/PartDesign/App/Feature.cpp +++ b/src/Mod/PartDesign/App/Feature.cpp @@ -100,6 +100,36 @@ TopoDS_Shape Feature::getSolid(const TopoDS_Shape& shape) return {}; } +// TODO REMOVE THIS METHOD AND DONT TRANSFER IN? +bool Feature::allowMultiSolid() const { + auto body = getFeatureBody(); + return body && !body->SingleSolid.getValue(); +} + +TopoShape Feature::getSolid(const TopoShape& shape, bool force) +{ + if (shape.isNull()) + throw Part::NullShapeException("Null shape"); + int count = shape.countSubShapes(TopAbs_SOLID); + if(count>1) { + if(allowMultiSolid()) { + auto res = shape; + res.fixSolidOrientation(); + return res; + } + throw Base::RuntimeError("Result has multiple solids.\n" + "To allow multiple solids, please set 'SingleSolid' property of the body to false"); + } + if(count) { + auto res = shape.getSubTopoShape(TopAbs_SOLID,1); + res.fixSolidOrientation(); + return res; + } + if (force) + return TopoShape(); + return shape; +} + int Feature::countSolids(const TopoDS_Shape& shape, TopAbs_ShapeEnum type) { int result = 0; @@ -240,6 +270,15 @@ TopoDS_Shape Feature::makeShapeFromPlane(const App::DocumentObject* obj) return builder.Shape(); } +TopoShape Feature::makeTopoShapeFromPlane(const App::DocumentObject* obj) +{ + BRepBuilderAPI_MakeFace builder(makePlnFromPlane(obj)); + if (!builder.IsDone()) + throw Base::CADKernelError("Feature: Could not create shape from base plane"); + + return TopoShape(obj->getID(), nullptr, builder.Shape()); +} + Body* Feature::getFeatureBody() const { auto body = Base::freecad_dynamic_cast(_Body.getValue()); diff --git a/src/Mod/PartDesign/App/Feature.h b/src/Mod/PartDesign/App/Feature.h index 8c47700333..fe2352fff6 100644 --- a/src/Mod/PartDesign/App/Feature.h +++ b/src/Mod/PartDesign/App/Feature.h @@ -95,6 +95,7 @@ protected: * Get a solid of the given shape. If no solid is found an exception is raised. */ static TopoDS_Shape getSolid(const TopoDS_Shape&); + TopoShape getSolid(const TopoShape &, bool force = true); static int countSolids(const TopoDS_Shape&, TopAbs_ShapeEnum type = TopAbs_SOLID ); /// Grab any point from the given face @@ -102,6 +103,7 @@ protected: /// Make a shape from a base plane (convenience method) static gp_Pln makePlnFromPlane(const App::DocumentObject* obj); static TopoDS_Shape makeShapeFromPlane(const App::DocumentObject* obj); + static TopoShape makeTopoShapeFromPlane(const App::DocumentObject* obj); }; using FeaturePython = App::FeaturePythonT; diff --git a/src/Mod/PartDesign/App/FeatureAddSub.cpp b/src/Mod/PartDesign/App/FeatureAddSub.cpp index 2d4b31fae4..fd67c1cb0c 100644 --- a/src/Mod/PartDesign/App/FeatureAddSub.cpp +++ b/src/Mod/PartDesign/App/FeatureAddSub.cpp @@ -83,6 +83,16 @@ TopoDS_Shape FeatureAddSub::refineShapeIfActive(const TopoDS_Shape& oldShape) co return oldShape; } +TopoShape FeatureAddSub::refineShapeIfActive(const TopoShape& oldShape) const +{ + if (this->Refine.getValue()) { + TopoShape shape(oldShape); +// this->fixShape(shape); + return shape.makeElementRefine(); + } + return oldShape; +} + void FeatureAddSub::getAddSubShape(Part::TopoShape &addShape, Part::TopoShape &subShape) { if (addSubType == Additive) diff --git a/src/Mod/PartDesign/App/FeatureAddSub.h b/src/Mod/PartDesign/App/FeatureAddSub.h index d9a32cfabd..30f73e48ee 100644 --- a/src/Mod/PartDesign/App/FeatureAddSub.h +++ b/src/Mod/PartDesign/App/FeatureAddSub.h @@ -55,6 +55,7 @@ protected: Type addSubType{Additive}; TopoDS_Shape refineShapeIfActive(const TopoDS_Shape&) const; + TopoShape refineShapeIfActive(const TopoShape&) const; }; using FeatureAddSubPython = App::FeaturePythonT; diff --git a/src/Mod/PartDesign/App/FeatureExtrude.cpp b/src/Mod/PartDesign/App/FeatureExtrude.cpp index 6e971d9c7a..805d278559 100644 --- a/src/Mod/PartDesign/App/FeatureExtrude.cpp +++ b/src/Mod/PartDesign/App/FeatureExtrude.cpp @@ -40,6 +40,9 @@ #include #include "FeatureExtrude.h" +#include "Mod/Part/App/TopoShapeOpCode.h" + +FC_LOG_LEVEL_INIT("PartDesign", true, true) using namespace PartDesign; @@ -246,6 +249,63 @@ void FeatureExtrude::generatePrism(TopoDS_Shape& prism, } } +void FeatureExtrude::generatePrism(TopoShape& prism, + TopoShape sketchTopoShape, + const std::string& method, + const gp_Dir& dir, + const double L, + const double L2, + const bool midplane, + const bool reversed) +{ + auto sketchShape = sketchTopoShape.getShape(); + if (method == "Length" || method == "TwoLengths" || method == "ThroughAll") { + double Ltotal = L; + double Loffset = 0.; + if (method == "ThroughAll") + Ltotal = getThroughAllLength(); + + + if (method == "TwoLengths") { + // midplane makes no sense here + Ltotal += L2; + if (reversed) + Loffset = -L; + else if (midplane) + Loffset = -0.5 * (L2 + L); + else + Loffset = -L2; + } else if (midplane) + Loffset = -Ltotal/2; + + if (method == "TwoLengths" || midplane) { + gp_Trsf mov; + mov.SetTranslation(Loffset * gp_Vec(dir)); + TopLoc_Location loc(mov); + sketchTopoShape.move(loc); + } else if (reversed) + Ltotal *= -1.0; + + // Without taper angle we create a prism because its shells are in every case no B-splines and can therefore + // be use as support for further features like Pads, Lofts etc. B-spline shells can break certain features, + // see e.g. https://forum.freecad.org/viewtopic.php?p=560785#p560785 + // It is better not to use BRepFeat_MakePrism here even if we have a support because the + // resulting shape creates problems with Pocket + try { + prism.makeElementPrism(sketchTopoShape, Ltotal*gp_Vec(dir)); // finite prism + }catch(Standard_Failure &) { + throw Base::RuntimeError("FeatureExtrusion: Length: Could not extrude the sketch!"); + } + } + else { + std::stringstream str; + str << "FeatureExtrusion: Internal error: Unknown method '" + << method << "' for generatePrism()"; + throw Base::RuntimeError(str.str()); + } + +} + void FeatureExtrude::generateTaperedPrism(TopoDS_Shape& prism, const TopoDS_Shape& sketchshape, const std::string& method, @@ -349,3 +409,270 @@ void FeatureExtrude::updateProperties(const std::string &method) Reversed.setReadOnly(!isReversedEnabled); UpToFace.setReadOnly(!isUpToFaceEnabled); } + +void FeatureExtrude::setupObject() +{ + ProfileBased::setupObject(); +} + +App::DocumentObjectExecReturn *FeatureExtrude::buildExtrusion(ExtrudeOptions options) +{ + bool makeface = options.testFlag(ExtrudeOption::MakeFace); + bool fuse = options.testFlag(ExtrudeOption::MakeFuse); + bool legacyPocket = options.testFlag(ExtrudeOption::LegacyPocket); + bool inverseDirection = options.testFlag(ExtrudeOption::InverseDirection); + + std::string method(Type.getValueAsString()); + + // Validate parameters + double L = Length.getValue(); + if ((method == "Length") && (L < Precision::Confusion())) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Length too small")); + double L2 = 0; + if ((method == "TwoLengths")) { + L2 = Length2.getValue(); + if (std::abs(L2) < Precision::Confusion()) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Second length too small")); + } + + Part::Feature* obj = nullptr; + TopoShape sketchshape; + try { + obj = getVerifiedObject(); + if (makeface) { + sketchshape = getVerifiedFace(); + } else { + std::vector shapes; + bool hasEdges = false; + auto subs = Profile.getSubValues(false); + if (subs.empty()) + subs.emplace_back(""); + bool failed = false; + for (auto & sub : subs) { + if (sub.empty() && subs.size()>1) + continue; + TopoShape shape = Part::Feature::getTopoShape(obj, sub.c_str(), true); + if (shape.isNull()) { + FC_ERR(getFullName() << ": failed to get profile shape " + << obj->getFullName() << "." << sub); + failed = true; + } + hasEdges = hasEdges || shape.hasSubShape(TopAbs_EDGE); + shapes.push_back(shape); + } + if (failed) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Failed to obtain profile shape")); + if (hasEdges) + sketchshape.makeElementWires(shapes); + else + sketchshape.makeElementCompound(shapes, nullptr, TopoShape::SingleShapeCompoundCreationPolicy::returnShape); + } + } catch (const Base::Exception& e) { + return new App::DocumentObjectExecReturn(e.what()); + } catch (const Standard_Failure& e) { + return new App::DocumentObjectExecReturn(e.GetMessageString()); + } + + // if the Base property has a valid shape, fuse the prism into it + TopoShape base = getBaseTopoShape(true); + + // get the normal vector of the sketch + Base::Vector3d SketchVector = getProfileNormal(); + + try { + this->positionByPrevious(); + auto invObjLoc = getLocation().Inverted(); + + auto invTrsf = invObjLoc.Transformation(); + + base.move(invObjLoc); + + Base::Vector3d paddingDirection = computeDirection(SketchVector); + + // create vector in padding direction with length 1 + gp_Dir dir(paddingDirection.x, paddingDirection.y, paddingDirection.z); + + // The length of a gp_Dir is 1 so the resulting pad would have + // the length L in the direction of dir. But we want to have its height in the + // direction of the normal vector. + // Therefore we must multiply L by the factor that is necessary + // to make dir as long that its projection to the SketchVector + // equals the SketchVector. + // This is the scalar product of both vectors. + // Since the pad length cannot be negative, the factor must not be negative. + + double factor = fabs(dir * gp_Dir(SketchVector.x, SketchVector.y, SketchVector.z)); + + // factor would be zero if vectors are orthogonal + if (factor < Precision::Confusion()) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", + "Creation failed because direction is orthogonal to sketch's normal vector")); + + // perform the length correction if not along custom vector + if (AlongSketchNormal.getValue()) { + L = L / factor; + L2 = L2 / factor; + } + + // explicitly set the Direction so that the dialog shows also the used direction + // if the sketch's normal vector was used + Direction.setValue(paddingDirection); + + dir.Transform(invTrsf); + + if (sketchshape.isNull()) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", + "Creating a face from sketch failed")); + sketchshape.move(invObjLoc); + + TopoShape prism(0,getDocument()->getStringHasher()); + + if (method == "UpToFirst" || method == "UpToLast" || method == "UpToFace") { + // Note: This will return an unlimited planar face if support is a datum plane + TopoShape supportface = getSupportFace(); + supportface.move(invObjLoc); + + if (Reversed.getValue()) + dir.Reverse(); + + // Find a valid face or datum plane to extrude up to + TopoShape upToFace; + if (method == "UpToFace") { + getUpToFaceFromLinkSub(upToFace, UpToFace); + upToFace.move(invObjLoc); + } + getUpToFace(upToFace, base, supportface, sketchshape, method, dir); + addOffsetToFace(upToFace, dir, Offset.getValue()); + + if (!supportface.hasSubShape(TopAbs_WIRE)) + supportface = TopoShape(); + if (legacyPocket) { + auto mode = base.isNull() ? TopoShape::PrismMode::None + : TopoShape::PrismMode::CutFromBase; + prism = base.makeElementPrismUntil(sketchshape, supportface, upToFace, + dir, mode, false/*CheckUpToFaceLimits.getValue()*/); + // DO NOT assign id to the generated prism, because this prism is + // actually the final result. We obtain the subtracted shape by cut + // this prism with the original base. Assigning a minus self id here + // will mess up with preselection highlight. It is enough to re-tag + // the profile shape above. + // + // prism.Tag = -this->getID(); + + // And the really expensive way to get the SubShape... + try { + TopoShape result(0,getDocument()->getStringHasher()); + if (base.isNull()) + result = prism; + else + result.makeElementCut({base,prism}); + result = refineShapeIfActive(result); + this->AddSubShape.setValue(result); + }catch(Standard_Failure &) { + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Up to face: Could not get SubShape!")); + } + + if (getAddSubType() == Additive) + prism = base.makeElementFuse(this->AddSubShape.getShape()); + else + prism = refineShapeIfActive(prism); + + this->Shape.setValue(getSolid(prism)); + return App::DocumentObject::StdReturn; + } + prism.makeElementPrismUntil(base, sketchshape, supportface, upToFace, + dir, TopoShape::PrismMode::None, false /*CheckUpToFaceLimits.getValue()*/); + } else { + Part::ExtrusionParameters params; + params.dir = dir; + params.solid = makeface; + params.taperAngleFwd = this->TaperAngle.getValue() * M_PI / 180.0; + params.taperAngleRev = this->TaperAngle2.getValue() * M_PI / 180.0; + if (L2 == 0.0 && Midplane.getValue()) { + params.lengthFwd = L/2; + params.lengthRev = L/2; + if (params.taperAngleRev == 0.0) + params.taperAngleRev = params.taperAngleFwd; + } else { + params.lengthFwd = L; + params.lengthRev = L2; + } + if (std::fabs(params.taperAngleFwd) >= Precision::Angular() + || std::fabs(params.taperAngleRev) >= Precision::Angular() ) { + if (fabs(params.taperAngleFwd) > M_PI * 0.5 - Precision::Angular() + || fabs(params.taperAngleRev) > M_PI * 0.5 - Precision::Angular()) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", + "Magnitude of taper angle matches or exceeds 90 degrees")); + if (Reversed.getValue()) + params.dir.Reverse(); + std::vector drafts; + Part::ExtrusionHelper::makeElementDraft(params, sketchshape, drafts); + if (drafts.empty()) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Padding with draft angle failed")); + prism.makeElementCompound(drafts, nullptr, TopoShape::SingleShapeCompoundCreationPolicy::returnShape); + + } else + generatePrism(prism, sketchshape, method, dir, L, L2, + Midplane.getValue(), Reversed.getValue()); + } + + // set the additive shape property for later usage in e.g. pattern + prism = refineShapeIfActive(prism); + this->AddSubShape.setValue(prism); + + if (!base.isNull() && fuse) { + prism.Tag = -this->getID(); + + // Let's call algorithm computing a fuse operation: + TopoShape result(0,getDocument()->getStringHasher()); + try { + const char *maker; + switch (getAddSubType()) { + case Subtractive: + maker = Part::OpCodes::Cut; + break; + default: + maker = Part::OpCodes::Fuse; + } + result.makeElementBoolean(maker, {base,prism}); + }catch(Standard_Failure &){ + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", + "Fusion with base feature failed")); + } + // we have to get the solids (fuse sometimes creates compounds) + auto solRes = this->getSolid(result); + // lets check if the result is a solid + if (solRes.isNull()) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", + "Resulting shape is not a solid")); + + solRes = refineShapeIfActive(solRes); + this->Shape.setValue(getSolid(solRes)); + } else if (prism.hasSubShape(TopAbs_SOLID)) { + if (prism.countSubShapes(TopAbs_SOLID) > 1) + prism.makeElementFuse(prism.getSubTopoShapes(TopAbs_SOLID)); + prism = refineShapeIfActive(prism); + this->Shape.setValue(getSolid(prism)); + } else { + prism = refineShapeIfActive(prism); + this->Shape.setValue(prism); + } + + // eventually disable some settings that are not valid for the current method + updateProperties(method); + + return App::DocumentObject::StdReturn; + } + catch (Standard_Failure& e) { + if (std::string(e.GetMessageString()) == "TopoDS::Face") + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", + "Could not create face from sketch.\n" + "Intersecting sketch entities or multiple faces in a sketch are not allowed.")); + else + return new App::DocumentObjectExecReturn(e.GetMessageString()); + } + catch (Base::Exception& e) { + return new App::DocumentObjectExecReturn(e.what()); + } + +} diff --git a/src/Mod/PartDesign/App/FeatureExtrude.h b/src/Mod/PartDesign/App/FeatureExtrude.h index 2bcdeb6832..9ba2945ee8 100644 --- a/src/Mod/PartDesign/App/FeatureExtrude.h +++ b/src/Mod/PartDesign/App/FeatureExtrude.h @@ -60,12 +60,25 @@ public: /** @name methods override feature */ //@{ short mustExecute() const override; + void setupObject() override; //@} protected: Base::Vector3d computeDirection(const Base::Vector3d& sketchVector); bool hasTaperedAngle() const; + /// Options for buildExtrusion() + enum class ExtrudeOption { + MakeFace = 1, + MakeFuse = 2, + LegacyPocket = 4, + InverseDirection = 8, + }; + + using ExtrudeOptions = Base::Flags; + + App::DocumentObjectExecReturn *buildExtrusion(ExtrudeOptions options); + /** * Generates an extrusion of the input sketchshape and stores it in the given \a prism */ @@ -78,6 +91,15 @@ protected: const bool midplane, const bool reversed); + void generatePrism(TopoShape& prism, + TopoShape sketchshape, + const std::string& method, + const gp_Dir& direction, + const double L, + const double L2, + const bool midplane, + const bool reversed); + // See BRepFeat_MakePrism enum PrismMode { CutFromBase = 0, @@ -120,5 +142,6 @@ protected: } //namespace PartDesign +ENABLE_BITMASK_OPERATORS(PartDesign::FeatureExtrude::ExtrudeOption) #endif // PARTDESIGN_FEATURE_EXTRUDE_H diff --git a/src/Mod/PartDesign/App/FeatureSketchBased.cpp b/src/Mod/PartDesign/App/FeatureSketchBased.cpp index 373cc9a476..b89d14e5c7 100644 --- a/src/Mod/PartDesign/App/FeatureSketchBased.cpp +++ b/src/Mod/PartDesign/App/FeatureSketchBased.cpp @@ -62,6 +62,8 @@ #include "DatumPlane.h" +FC_LOG_LEVEL_INIT("PartDesign",true,true); + using namespace PartDesign; PROPERTY_SOURCE(PartDesign::ProfileBased, PartDesign::FeatureAddSub) @@ -253,6 +255,134 @@ TopoDS_Shape ProfileBased::getVerifiedFace(bool silent) const { return TopoDS_Face(); } +TopoShape ProfileBased::getTopoShapeVerifiedFace(bool silent, + bool doFit, + bool allowOpen, + const App::DocumentObject *profile, + const std::vector &_subs) const +{ + auto obj = profile ? profile : Profile.getValue(); + if(!obj || !obj->getNameInDocument()) { + if(silent) + return TopoShape(); + throw Base::ValueError("No profile linked"); + } + const auto &subs = profile ? _subs : Profile.getSubValues(); + try { + TopoShape shape; + if(AllowMultiFace.getValue()) { + if (subs.empty()) + shape = Part::Feature::getTopoShape(obj); + else { + std::vector shapes; + for (auto &sub : subs) { + auto subshape = Part::Feature::getTopoShape( + obj, sub.c_str(), /*needSubElement*/true); + if (subshape.isNull()) + FC_THROWM(Base::CADKernelError, "Sub shape not found: " << + obj->getFullName() << "." << sub); + shapes.push_back(subshape); + } + shape.makeElementCompound(shapes); + } + } else { + std::string sub; + if(!obj->getTypeId().isDerivedFrom(Part::Part2DObject::getClassTypeId())) { + if(!subs.empty()) + sub = subs[0]; + } + shape = Part::Feature::getTopoShape(obj,sub.c_str(),!sub.empty()); + } + if(shape.isNull()) { + if (silent) + return shape; + throw Base::CADKernelError("Linked shape object is empty"); + } + TopoShape openshape; + if(!shape.hasSubShape(TopAbs_FACE)) { + try { + if(!shape.hasSubShape(TopAbs_WIRE)) + shape = shape.makeElementWires(); + if(shape.hasSubShape(TopAbs_WIRE)) { + shape.Hasher = getDocument()->getStringHasher(); + if (allowOpen) { + std::vector openwires; + std::vector wires; + for (auto &wire : shape.getSubTopoShapes(TopAbs_WIRE)) { + if (!wire.isClosed()) + openwires.push_back(wire); + else + wires.push_back(wire); + } + if (openwires.size()) { + openshape.makeElementCompound(openwires, nullptr, TopoShape ::SingleShapeCompoundCreationPolicy::returnShape); + if (wires.empty()) + shape = TopoShape(); + else + shape.makeElementCompound(wires, nullptr, TopoShape ::SingleShapeCompoundCreationPolicy::returnShape); + } + } + if (!shape.isNull()) { + if (AllowMultiFace.getValue()) + shape = shape.makeElementFace(); // default to use FaceMakerBullseye + else + shape = shape.makeElementFace(nullptr, "Part::FaceMakerCheese"); + } + } + } catch (const Base::Exception &) { + if (silent) + return TopoShape(); + throw; + } catch (const Standard_Failure &) { + if (silent) + return TopoShape(); + throw; + } + } + int count = shape.countSubShapes(TopAbs_FACE); + if(!count && !allowOpen) { + if(silent) + return TopoShape(); + throw Base::CADKernelError("Cannot make face from profile"); + } + +// if (doFit && (std::abs(Fit.getValue()) > Precision::Confusion() +// || std::abs(InnerFit.getValue()) > Precision::Confusion())) { +// +// if (!shape.isNull()) +// shape = shape.makEOffsetFace(Fit.getValue(), +// InnerFit.getValue(), +// static_cast(FitJoin.getValue()), +// static_cast(InnerFitJoin.getValue())); +// if (!openshape.isNull()) +// openshape.makEOffset2D(Fit.getValue()); +// } + + if (!openshape.isNull()) { + if (shape.isNull()) + shape = openshape; + else + shape.makeElementCompound({shape, openshape}); + } + if(count>1) { + if(AllowMultiFace.getValue() +// || allowMultiSolid() + || obj->isDerivedFrom(Part::Part2DObject::getClassTypeId())) + return shape; + FC_WARN("Found more than one face from profile"); + } + if (!openshape.isNull()) + return shape; + if (count) + return shape.getSubTopoShape(TopAbs_FACE,1); + return shape; + }catch (Standard_Failure &) { + if(silent) + return TopoShape(); + throw; + } +} + std::vector ProfileBased::getProfileWires() const { std::vector result; @@ -292,6 +422,22 @@ std::vector ProfileBased::getProfileWires() const { return result; } +std::vector ProfileBased::getTopoShapeProfileWires() const { + // shape copy is a workaround for an obscure OCC bug which leads to empty + // tessellations for some faces. Making an explicit copy of the linked + // shape seems to fix it. The error mostly happens when re-computing the + // shape but sometimes also for the first time + auto shape = getProfileShape().makeElementCopy(); + + if(shape.hasSubShape(TopAbs_WIRE)) + return shape.getSubTopoShapes(TopAbs_WIRE); + + auto wires = shape.makeElementWires().getSubTopoShapes(TopAbs_WIRE); + if(wires.empty()) + throw Part::NullShapeException("Linked shape object is not a wire"); + return wires; +} + // Note: We cannot return a reference, because it will become Null. // Not clear where, because we check for IsNull() here, but as soon as it is passed out of // this method, it becomes null! @@ -356,6 +502,34 @@ TopoDS_Face ProfileBased::getSupportFace(const App::PropertyLinkSub& link) const return face; } +TopoShape ProfileBased::getTopoShapeSupportFace() const { + TopoShape shape; + const Part::Part2DObject* sketch = getVerifiedSketch(true); + if (!sketch) + shape = getVerifiedFace(); + else if (sketch->MapMode.getValue() == Attacher::mmFlatFace && sketch->AttachmentSupport.getValue()) { + const auto &Support = sketch->AttachmentSupport; + App::DocumentObject* ref = Support.getValue(); + shape = Part::Feature::getTopoShape( + ref, Support.getSubValues().size() ? Support.getSubValues()[0].c_str() : "", true); + } + if (!shape.isNull()) { + if (shape.shapeType(true) != TopAbs_FACE) { + if (!shape.hasSubShape(TopAbs_FACE)) + throw Base::ValueError("Null face in SketchBased::getSupportFace()!"); + shape = shape.getSubTopoShape(TopAbs_FACE, 1); + } + gp_Pln pln; + if (!shape.findPlane(pln)) + throw Base::TypeError("No planar face in SketchBased::getSupportFace()!"); + + return shape; + } + if (!sketch) + throw Base::RuntimeError("No planar support"); + return Feature::makeShapeFromPlane(sketch); +} + int ProfileBased::getSketchAxisCount() const { Part::Part2DObject* sketch = static_cast(Profile.getValue()); @@ -540,6 +714,54 @@ void ProfileBased::getUpToFace(TopoDS_Face& upToFace, } } +void ProfileBased::getUpToFace(TopoShape& upToFace, + const TopoShape& support, + const TopoShape& supportface, + const TopoShape& sketchshape, + const std::string& method, + gp_Dir& dir) +{ + if ((method == "UpToLast") || (method == "UpToFirst")) { + std::vector cfaces = Part::findAllFacesCutBy(support, sketchshape, dir); + if (cfaces.empty()) + throw Base::ValueError("SketchBased: No faces found in this direction"); + + // Find nearest/furthest face + std::vector::const_iterator it, it_near, it_far; + it_near = it_far = cfaces.begin(); + for (it = cfaces.begin(); it != cfaces.end(); it++) + if (it->distsq > it_far->distsq) + it_far = it; + else if (it->distsq < it_near->distsq) + it_near = it; + upToFace = (method == "UpToLast" ? it_far->face : it_near->face); + } else if (Part::findAllFacesCutBy(upToFace, sketchshape, dir).empty()) + dir = -dir; + + if (upToFace.shapeType(true) != TopAbs_FACE) { + if (!upToFace.hasSubShape(TopAbs_FACE)) + throw Base::ValueError("SketchBased: Up to face: No face found"); + upToFace = upToFace.getSubTopoShape(TopAbs_FACE, 1); + } + + TopoDS_Face face = TopoDS::Face(upToFace.getShape()); + + // Check that the upToFace does not intersect the sketch face and + // is not parallel to the extrusion direction (for simplicity, supportface is used instead of sketchshape) + BRepAdaptor_Surface adapt1(TopoDS::Face(supportface.getShape())); + BRepAdaptor_Surface adapt2(face); + + if (adapt2.GetType() == GeomAbs_Plane) { + if (adapt1.Plane().Axis().IsNormal(adapt2.Plane().Axis(), Precision::Confusion())) + throw Base::ValueError("SketchBased: Up to face: Must not be parallel to extrusion direction!"); + } + + // We must measure from sketchshape, not supportface, here + BRepExtrema_DistShapeShape distSS(sketchshape.getShape(), face); + if (distSS.Value() < Precision::Confusion()) + throw Base::ValueError("SketchBased: Up to face: Must not intersect sketch!"); +} + void ProfileBased::addOffsetToFace(TopoDS_Face& upToFace, const gp_Dir& dir, double offset) { // Move the face in the extrusion direction @@ -564,6 +786,18 @@ void ProfileBased::addOffsetToFace(TopoDS_Face& upToFace, const gp_Dir& dir, dou } } +void ProfileBased::addOffsetToFace(TopoShape& upToFace, const gp_Dir& dir, double offset) +{ + // Move the face in the extrusion direction + // TODO: For non-planar faces, we could consider offsetting the surface + if (fabs(offset) > Precision::Confusion()) { + gp_Trsf mov; + mov.SetTranslation(offset * gp_Vec(dir)); + TopLoc_Location loc(mov); + upToFace.move(loc); + } +} + double ProfileBased::getThroughAllLength() const { TopoDS_Shape profileshape; @@ -739,6 +973,13 @@ bool ProfileBased::checkLineCrossesFace(const gp_Lin& line, const TopoDS_Face& f void ProfileBased::remapSupportShape(const TopoDS_Shape & newShape) { +#if FC_USE_TNP_FIX + (void)newShape; + // Realthunder: with the new topological naming, I don't think this function + // is necessary. A missing element will cause an explicitly error, and the + // user will be force to manually select the element. Various editors, such + // as dress up editors, can perform element guessing when activated. +#else TopTools_IndexedMapOfShape faceMap; TopExp::MapShapes(newShape, TopAbs_FACE, faceMap); @@ -830,6 +1071,7 @@ void ProfileBased::remapSupportShape(const TopoDS_Shape & newShape) link->setValue(this, newSubValues); } } +#endif } namespace PartDesign { diff --git a/src/Mod/PartDesign/App/FeatureSketchBased.h b/src/Mod/PartDesign/App/FeatureSketchBased.h index 49bd99702b..be4204d334 100644 --- a/src/Mod/PartDesign/App/FeatureSketchBased.h +++ b/src/Mod/PartDesign/App/FeatureSketchBased.h @@ -99,15 +99,34 @@ public: */ TopoDS_Shape getVerifiedFace(bool silent = false) const; + /** + * Verifies the linked Object and returns the shape used as profile + * @param silent: if profile property is malformed and the parameter is true + * silently returns nullptr, otherwise throw a Base::Exception. + * Default is false. + * @param doFit: Whether to fitting according to the 'Fit' property + * @param allowOpen: Whether allow open wire + * @param profile: optional profile object, if not given then use 'Profile' property + * @param subs: optional profile sub-object names, if not given then use 'Profile' property + */ + TopoShape getTopoShapeVerifiedFace(bool silent = false, + bool doFit = true, + bool allowOpen = false, + const App::DocumentObject *profile = nullptr, + const std::vector &subs = {}) const; + /// Returns the wires the sketch is composed of std::vector getProfileWires() const; + std::vector getTopoShapeProfileWires() const; + /// Returns the face of the sketch support (if any) const TopoDS_Face getSupportFace() const; + TopoShape getTopoShapeSupportFace() const; Base::Vector3d getProfileNormal() const; - Part::TopoShape getProfileShape() const; + TopoShape getProfileShape() const; /// retrieves the number of axes in the linked sketch (defined as construction lines) int getSketchAxisCount() const; @@ -142,6 +161,22 @@ protected: static void addOffsetToFace(TopoDS_Face& upToFace, const gp_Dir& dir, double offset); + /// Extract a face from a given LinkSub + static void getUpToFaceFromLinkSub(TopoShape& upToFace, + const App::PropertyLinkSub& refFace); + + /// Find a valid face to extrude up to + static void getUpToFace(TopoShape& upToFace, + const TopoShape& support, + const TopoShape& supportface, + const TopoShape& sketchshape, + const std::string& method, + gp_Dir& dir); + + /// Add an offset to the face + static void addOffsetToFace(TopoShape& upToFace, + const gp_Dir& dir, + double offset); /// Check whether the wire after projection on the face is inside the face static bool checkWireInsideFace(const TopoDS_Wire& wire, From c9d8bdf13d805883f4703960a5cc96411a295987 Mon Sep 17 00:00:00 2001 From: bgbsww Date: Sun, 7 Apr 2024 12:39:32 -0400 Subject: [PATCH 02/24] Toponaming/Part: Add deprecation comments, clean up code --- src/Mod/Part/App/PartFeature.cpp | 29 +- src/Mod/Part/App/PartFeature.h | 12 +- src/Mod/Part/App/TopoShape.h | 44 +-- src/Mod/Part/App/TopoShapeExpansion.cpp | 6 +- src/Mod/PartDesign/App/Feature.cpp | 61 ++-- src/Mod/PartDesign/App/Feature.h | 6 +- src/Mod/PartDesign/App/FeatureAddSub.cpp | 3 +- src/Mod/PartDesign/App/FeatureAddSub.h | 1 + src/Mod/PartDesign/App/FeatureExtrude.cpp | 263 ++++++++++------ src/Mod/PartDesign/App/FeatureExtrude.h | 5 +- src/Mod/PartDesign/App/FeaturePad.cpp | 8 + src/Mod/PartDesign/App/FeaturePrimitive.cpp | 67 +++- src/Mod/PartDesign/App/FeatureSketchBased.cpp | 297 ++++++++++++------ src/Mod/PartDesign/App/FeatureSketchBased.h | 21 +- src/Mod/Sketcher/App/SketchObject.cpp | 30 +- tests/src/Mod/Part/App/PartTestHelpers.cpp | 11 +- tests/src/Mod/Part/App/TopoShapeExpansion.cpp | 56 ++-- 17 files changed, 594 insertions(+), 326 deletions(-) diff --git a/src/Mod/Part/App/PartFeature.cpp b/src/Mod/Part/App/PartFeature.cpp index e569305e36..e46abd564c 100644 --- a/src/Mod/Part/App/PartFeature.cpp +++ b/src/Mod/Part/App/PartFeature.cpp @@ -1319,6 +1319,7 @@ template<> PyObject* Part::FeaturePython::getPyObject() { template class PartExport FeaturePythonT; } +// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. std::vector Part::findAllFacesCutBy( const TopoDS_Shape& shape, const TopoDS_Shape& face, const gp_Dir& dir) { @@ -1359,38 +1360,42 @@ std::vector Part::findAllFacesCutBy( return result; } -std::vector Part::findAllFacesCutBy( - const TopoShape& shape, const TopoShape& face, const gp_Dir& dir) +std::vector +Part::findAllFacesCutBy(const TopoShape& shape, const TopoShape& face, const gp_Dir& dir) { // Find the centre of gravity of the face GProp_GProps props; - BRepGProp::SurfaceProperties(face.getShape(),props); + BRepGProp::SurfaceProperties(face.getShape(), props); gp_Pnt cog = props.CentreOfMass(); // create a line through the centre of gravity gp_Lin line = gce_MakeLin(cog, dir); // Find intersection of line with all faces of the shape - std::vector result; + std::vector result; BRepIntCurveSurface_Inter mkSection; // TODO: Less precision than Confusion() should be OK? - for (mkSection.Init(shape.getShape(), line, Precision::Confusion()); mkSection.More(); mkSection.Next()) { + for (mkSection.Init(shape.getShape(), line, Precision::Confusion()); mkSection.More(); + mkSection.Next()) { gp_Pnt iPnt = mkSection.Pnt(); double dsq = cog.SquareDistance(iPnt); - if (dsq < Precision::Confusion()) - continue; // intersection with original face + if (dsq < Precision::Confusion()) { + continue; // intersection with original face + } // Find out which side of the original face the intersection is on gce_MakeDir mkDir(cog, iPnt); - if (!mkDir.IsDone()) - continue; // some error (appears highly unlikely to happen, though...) + if (!mkDir.IsDone()) { + continue; // some error (appears highly unlikely to happen, though...) + } - if (mkDir.Value().IsOpposite(dir, Precision::Confusion())) - continue; // wrong side of face (opposite to extrusion direction) + if (mkDir.Value().IsOpposite(dir, Precision::Confusion())) { + continue; // wrong side of face (opposite to extrusion direction) + } - cutFaces newF; + cutTopoShapeFaces newF; newF.face = mkSection.Face(); newF.face.mapSubElement(shape); newF.distsq = dsq; diff --git a/src/Mod/Part/App/PartFeature.h b/src/Mod/Part/App/PartFeature.h index 54bd009a69..e3259089e0 100644 --- a/src/Mod/Part/App/PartFeature.h +++ b/src/Mod/Part/App/PartFeature.h @@ -211,18 +211,24 @@ public: * Find all faces cut by a line through the centre of gravity of a given face * Useful for the "up to face" options to pocket or pad */ +// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. struct cutFaces { TopoDS_Face face; double distsq; }; +// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. PartExport std::vector findAllFacesCutBy(const TopoDS_Shape& shape, const TopoDS_Shape& face, const gp_Dir& dir); +struct cutTopoShapeFaces +{ + TopoShape face; + double distsq; +}; -PartExport - std::vector findAllFacesCutBy(const TopoShape& shape, - const TopoShape& face, const gp_Dir& dir); +PartExport std::vector +findAllFacesCutBy(const TopoShape& shape, const TopoShape& face, const gp_Dir& dir); /** * Check for intersection between the two shapes. Only solids are guaranteed to work properly diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index 74db8e5697..bc4ae8718f 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -1153,14 +1153,14 @@ public: * a self reference so that multiple operations can be carried out * for the same shape in the same line of code. */ - TopoShape &makeElementPrismUntil(const TopoShape &base, - const TopoShape& profile, - const TopoShape& supportFace, - const TopoShape& upToFace, - const gp_Dir& direction, - PrismMode mode, - Standard_Boolean checkLimits = Standard_True, - const char *op=nullptr); + TopoShape& makeElementPrismUntil(const TopoShape& base, + const TopoShape& profile, + const TopoShape& supportFace, + const TopoShape& upToFace, + const gp_Dir& direction, + PrismMode mode, + Standard_Boolean checkLimits = Standard_True, + const char* op = nullptr); /** Make a prism based on this shape that is either depression or protrusion of a profile shape up to a given face * @@ -1180,21 +1180,21 @@ public: * @return Return the generated new shape. The TopoShape itself is not modified. */ TopoShape makeElementPrismUntil(const TopoShape& profile, - const TopoShape& supportFace, - const TopoShape& upToFace, - const gp_Dir& direction, - PrismMode mode, - Standard_Boolean checkLimits = Standard_True, - const char *op=nullptr) const + const TopoShape& supportFace, + const TopoShape& upToFace, + const gp_Dir& direction, + PrismMode mode, + Standard_Boolean checkLimits = Standard_True, + const char* op = nullptr) const { - return TopoShape(0,Hasher).makeElementPrismUntil(*this, - profile, - supportFace, - upToFace, - direction, - mode, - checkLimits, - op); + return TopoShape(0, Hasher).makeElementPrismUntil(*this, + profile, + supportFace, + upToFace, + direction, + mode, + checkLimits, + op); } diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index ad69144905..556560ea22 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -4231,7 +4231,7 @@ TopoShape& TopoShape::makeElementPrism(const TopoShape& base, const gp_Vec& vec, return makeElementShape(mkPrism, base, op); } - TopoShape& TopoShape::makeElementPrismUntil(const TopoShape& _base, +TopoShape& TopoShape::makeElementPrismUntil(const TopoShape& _base, const TopoShape& profile, const TopoShape& supportFace, const TopoShape& __uptoface, @@ -4302,8 +4302,8 @@ TopoShape& TopoShape::makeElementPrism(const TopoShape& base, const gp_Vec& vec, if (remove_limits) { // Note: Using an unlimited face every time gives unnecessary failures for concave // faces - TopLoc_Location loc = face.Location(); BRepAdaptor_Surface adapt(face, - Standard_False); + TopLoc_Location loc = face.Location(); + BRepAdaptor_Surface adapt(face, Standard_False); // use the placement of the adapter, not of the upToFace loc = TopLoc_Location(adapt.Trsf()); BRepBuilderAPI_MakeFace mkFace(adapt.Surface().Surface(), Precision::Confusion()); diff --git a/src/Mod/PartDesign/App/Feature.cpp b/src/Mod/PartDesign/App/Feature.cpp index f3fe0df1aa..28d37c9c45 100644 --- a/src/Mod/PartDesign/App/Feature.cpp +++ b/src/Mod/PartDesign/App/Feature.cpp @@ -87,6 +87,7 @@ short Feature::mustExecute() const return Part::Feature::mustExecute(); } +// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. TopoDS_Shape Feature::getSolid(const TopoDS_Shape& shape) { if (shape.IsNull()) @@ -100,33 +101,30 @@ TopoDS_Shape Feature::getSolid(const TopoDS_Shape& shape) return {}; } -// TODO REMOVE THIS METHOD AND DONT TRANSFER IN? -bool Feature::allowMultiSolid() const { - auto body = getFeatureBody(); - return body && !body->SingleSolid.getValue(); -} - TopoShape Feature::getSolid(const TopoShape& shape, bool force) { - if (shape.isNull()) + if (shape.isNull()) { throw Part::NullShapeException("Null shape"); + } int count = shape.countSubShapes(TopAbs_SOLID); - if(count>1) { - if(allowMultiSolid()) { + if (count > 1) { + if (getFeatureBody()) { auto res = shape; res.fixSolidOrientation(); return res; } - throw Base::RuntimeError("Result has multiple solids.\n" - "To allow multiple solids, please set 'SingleSolid' property of the body to false"); + throw Base::RuntimeError( + "Result has multiple solids.\n" + "To allow multiple solids, please set 'SingleSolid' property of the body to false"); } - if(count) { - auto res = shape.getSubTopoShape(TopAbs_SOLID,1); + if (count) { + auto res = shape.getSubTopoShape(TopAbs_SOLID, 1); res.fixSolidOrientation(); return res; } - if (force) + if (force) { return TopoShape(); + } return shape; } @@ -206,29 +204,40 @@ const TopoDS_Shape& Feature::getBaseShape() const { return result; } -Part::TopoShape Feature::getBaseTopoShape(bool silent) const { +Part::TopoShape Feature::getBaseTopoShape(bool silent) const +{ Part::TopoShape result; const Part::Feature* BaseObject = getBaseObject(silent); - if (!BaseObject) + if (!BaseObject) { return result; + } - if(BaseObject != BaseFeature.getValue()) { - if (BaseObject->isDerivedFrom(PartDesign::ShapeBinder::getClassTypeId()) || - BaseObject->isDerivedFrom(PartDesign::SubShapeBinder::getClassTypeId())) - { - if(silent) + if (BaseObject != BaseFeature.getValue()) { + auto body = getFeatureBody(); + if (!body) { + if (silent) { return result; + } + throw Base::RuntimeError("Missing container body"); + } + if (BaseObject->isDerivedFrom(PartDesign::ShapeBinder::getClassTypeId()) + || BaseObject->isDerivedFrom(PartDesign::SubShapeBinder::getClassTypeId())) { + if (silent) { + return result; + } throw Base::ValueError("Base shape of shape binder cannot be used"); } } result = BaseObject->Shape.getShape(); - if(!silent) { - if (result.isNull()) + if (!silent) { + if (result.isNull()) { throw Base::ValueError("Base feature's TopoShape is invalid"); - if (!result.hasSubShape(TopAbs_SOLID)) + } + if (!result.hasSubShape(TopAbs_SOLID)) { throw Base::ValueError("Base feature's shape is not a solid"); + } } return result; } @@ -261,6 +270,7 @@ gp_Pln Feature::makePlnFromPlane(const App::DocumentObject* obj) return gp_Pln(gp_Pnt(pos.x,pos.y,pos.z), gp_Dir(normal.x,normal.y,normal.z)); } +// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. TopoDS_Shape Feature::makeShapeFromPlane(const App::DocumentObject* obj) { BRepBuilderAPI_MakeFace builder(makePlnFromPlane(obj)); @@ -273,8 +283,9 @@ TopoDS_Shape Feature::makeShapeFromPlane(const App::DocumentObject* obj) TopoShape Feature::makeTopoShapeFromPlane(const App::DocumentObject* obj) { BRepBuilderAPI_MakeFace builder(makePlnFromPlane(obj)); - if (!builder.IsDone()) + if (!builder.IsDone()) { throw Base::CADKernelError("Feature: Could not create shape from base plane"); + } return TopoShape(obj->getID(), nullptr, builder.Shape()); } diff --git a/src/Mod/PartDesign/App/Feature.h b/src/Mod/PartDesign/App/Feature.h index fe2352fff6..8f901f9eeb 100644 --- a/src/Mod/PartDesign/App/Feature.h +++ b/src/Mod/PartDesign/App/Feature.h @@ -94,14 +94,16 @@ protected: /** * Get a solid of the given shape. If no solid is found an exception is raised. */ + // TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. static TopoDS_Shape getSolid(const TopoDS_Shape&); - TopoShape getSolid(const TopoShape &, bool force = true); - static int countSolids(const TopoDS_Shape&, TopAbs_ShapeEnum type = TopAbs_SOLID ); + TopoShape getSolid(const TopoShape&, bool force = true); + static int countSolids(const TopoDS_Shape&, TopAbs_ShapeEnum type = TopAbs_SOLID); /// Grab any point from the given face static const gp_Pnt getPointFromFace(const TopoDS_Face& f); /// Make a shape from a base plane (convenience method) static gp_Pln makePlnFromPlane(const App::DocumentObject* obj); + // TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. static TopoDS_Shape makeShapeFromPlane(const App::DocumentObject* obj); static TopoShape makeTopoShapeFromPlane(const App::DocumentObject* obj); }; diff --git a/src/Mod/PartDesign/App/FeatureAddSub.cpp b/src/Mod/PartDesign/App/FeatureAddSub.cpp index fd67c1cb0c..c500c69ad9 100644 --- a/src/Mod/PartDesign/App/FeatureAddSub.cpp +++ b/src/Mod/PartDesign/App/FeatureAddSub.cpp @@ -64,6 +64,7 @@ short FeatureAddSub::mustExecute() const return PartDesign::Feature::mustExecute(); } +// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. TopoDS_Shape FeatureAddSub::refineShapeIfActive(const TopoDS_Shape& oldShape) const { if (this->Refine.getValue()) { @@ -87,7 +88,7 @@ TopoShape FeatureAddSub::refineShapeIfActive(const TopoShape& oldShape) const { if (this->Refine.getValue()) { TopoShape shape(oldShape); -// this->fixShape(shape); + // this->fixShape(shape); // Todo: Not clear that this is required return shape.makeElementRefine(); } return oldShape; diff --git a/src/Mod/PartDesign/App/FeatureAddSub.h b/src/Mod/PartDesign/App/FeatureAddSub.h index 30f73e48ee..19f0baf578 100644 --- a/src/Mod/PartDesign/App/FeatureAddSub.h +++ b/src/Mod/PartDesign/App/FeatureAddSub.h @@ -54,6 +54,7 @@ public: protected: Type addSubType{Additive}; + // TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. TopoDS_Shape refineShapeIfActive(const TopoDS_Shape&) const; TopoShape refineShapeIfActive(const TopoShape&) const; }; diff --git a/src/Mod/PartDesign/App/FeatureExtrude.cpp b/src/Mod/PartDesign/App/FeatureExtrude.cpp index 805d278559..1cbd35949d 100644 --- a/src/Mod/PartDesign/App/FeatureExtrude.cpp +++ b/src/Mod/PartDesign/App/FeatureExtrude.cpp @@ -38,9 +38,9 @@ #include #include #include +#include "Mod/Part/App/TopoShapeOpCode.h" #include "FeatureExtrude.h" -#include "Mod/Part/App/TopoShapeOpCode.h" FC_LOG_LEVEL_INIT("PartDesign", true, true) @@ -129,6 +129,7 @@ bool FeatureExtrude::hasTaperedAngle() const fabs(TaperAngle2.getValue()) > Base::toRadians(Precision::Angular()); } +// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. void FeatureExtrude::generatePrism(TopoDS_Shape& prism, const TopoDS_Shape& sketchshape, const std::string& method, @@ -262,48 +263,57 @@ void FeatureExtrude::generatePrism(TopoShape& prism, if (method == "Length" || method == "TwoLengths" || method == "ThroughAll") { double Ltotal = L; double Loffset = 0.; - if (method == "ThroughAll") + if (method == "ThroughAll") { Ltotal = getThroughAllLength(); + } if (method == "TwoLengths") { // midplane makes no sense here Ltotal += L2; - if (reversed) + if (reversed) { Loffset = -L; - else if (midplane) + } + else if (midplane) { Loffset = -0.5 * (L2 + L); - else + } + else { Loffset = -L2; - } else if (midplane) - Loffset = -Ltotal/2; + } + } + else if (midplane) { + Loffset = -Ltotal / 2; + } if (method == "TwoLengths" || midplane) { gp_Trsf mov; mov.SetTranslation(Loffset * gp_Vec(dir)); TopLoc_Location loc(mov); sketchTopoShape.move(loc); - } else if (reversed) + } + else if (reversed) { Ltotal *= -1.0; + } - // Without taper angle we create a prism because its shells are in every case no B-splines and can therefore - // be use as support for further features like Pads, Lofts etc. B-spline shells can break certain features, - // see e.g. https://forum.freecad.org/viewtopic.php?p=560785#p560785 - // It is better not to use BRepFeat_MakePrism here even if we have a support because the - // resulting shape creates problems with Pocket + // Without taper angle we create a prism because its shells are in every case no B-splines + // and can therefore be use as support for further features like Pads, Lofts etc. B-spline + // shells can break certain features, see e.g. + // https://forum.freecad.org/viewtopic.php?p=560785#p560785 It is better not to use + // BRepFeat_MakePrism here even if we have a support because the resulting shape creates + // problems with Pocket try { - prism.makeElementPrism(sketchTopoShape, Ltotal*gp_Vec(dir)); // finite prism - }catch(Standard_Failure &) { + prism.makeElementPrism(sketchTopoShape, Ltotal * gp_Vec(dir)); // finite prism + } + catch (Standard_Failure&) { throw Base::RuntimeError("FeatureExtrusion: Length: Could not extrude the sketch!"); } } else { std::stringstream str; - str << "FeatureExtrusion: Internal error: Unknown method '" - << method << "' for generatePrism()"; + str << "FeatureExtrusion: Internal error: Unknown method '" << method + << "' for generatePrism()"; throw Base::RuntimeError(str.str()); } - } void FeatureExtrude::generateTaperedPrism(TopoDS_Shape& prism, @@ -415,7 +425,7 @@ void FeatureExtrude::setupObject() ProfileBased::setupObject(); } -App::DocumentObjectExecReturn *FeatureExtrude::buildExtrusion(ExtrudeOptions options) +App::DocumentObjectExecReturn* FeatureExtrude::buildExtrusion(ExtrudeOptions options) { bool makeface = options.testFlag(ExtrudeOption::MakeFace); bool fuse = options.testFlag(ExtrudeOption::MakeFuse); @@ -426,13 +436,17 @@ App::DocumentObjectExecReturn *FeatureExtrude::buildExtrusion(ExtrudeOptions opt // Validate parameters double L = Length.getValue(); - if ((method == "Length") && (L < Precision::Confusion())) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Length too small")); + if ((method == "Length") && (L < Precision::Confusion())) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Length too small")); + } double L2 = 0; if ((method == "TwoLengths")) { L2 = Length2.getValue(); - if (std::abs(L2) < Precision::Confusion()) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Second length too small")); + if (std::abs(L2) < Precision::Confusion()) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Second length too small")); + } } Part::Feature* obj = nullptr; @@ -440,36 +454,48 @@ App::DocumentObjectExecReturn *FeatureExtrude::buildExtrusion(ExtrudeOptions opt try { obj = getVerifiedObject(); if (makeface) { - sketchshape = getVerifiedFace(); - } else { + sketchshape = getTopoShapeVerifiedFace(); + } + else { std::vector shapes; bool hasEdges = false; auto subs = Profile.getSubValues(false); - if (subs.empty()) + if (subs.empty()) { subs.emplace_back(""); + } bool failed = false; - for (auto & sub : subs) { - if (sub.empty() && subs.size()>1) + for (auto& sub : subs) { + if (sub.empty() && subs.size() > 1) { continue; + } TopoShape shape = Part::Feature::getTopoShape(obj, sub.c_str(), true); if (shape.isNull()) { - FC_ERR(getFullName() << ": failed to get profile shape " - << obj->getFullName() << "." << sub); + FC_ERR(getFullName() + << ": failed to get profile shape " << obj->getFullName() << "." << sub); failed = true; } hasEdges = hasEdges || shape.hasSubShape(TopAbs_EDGE); shapes.push_back(shape); } - if (failed) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Failed to obtain profile shape")); - if (hasEdges) + if (failed) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Failed to obtain profile shape")); + } + if (hasEdges) { sketchshape.makeElementWires(shapes); - else - sketchshape.makeElementCompound(shapes, nullptr, TopoShape::SingleShapeCompoundCreationPolicy::returnShape); + } + else { + sketchshape.makeElementCompound( + shapes, + nullptr, + TopoShape::SingleShapeCompoundCreationPolicy::returnShape); + } } - } catch (const Base::Exception& e) { + } + catch (const Base::Exception& e) { return new App::DocumentObjectExecReturn(e.what()); - } catch (const Standard_Failure& e) { + } + catch (const Standard_Failure& e) { return new App::DocumentObjectExecReturn(e.GetMessageString()); } @@ -504,9 +530,11 @@ App::DocumentObjectExecReturn *FeatureExtrude::buildExtrusion(ExtrudeOptions opt double factor = fabs(dir * gp_Dir(SketchVector.x, SketchVector.y, SketchVector.z)); // factor would be zero if vectors are orthogonal - if (factor < Precision::Confusion()) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", - "Creation failed because direction is orthogonal to sketch's normal vector")); + if (factor < Precision::Confusion()) { + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP( + "Exception", + "Creation failed because direction is orthogonal to sketch's normal vector")); + } // perform the length correction if not along custom vector if (AlongSketchNormal.getValue()) { @@ -520,20 +548,22 @@ App::DocumentObjectExecReturn *FeatureExtrude::buildExtrusion(ExtrudeOptions opt dir.Transform(invTrsf); - if (sketchshape.isNull()) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", - "Creating a face from sketch failed")); + if (sketchshape.isNull()) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Creating a face from sketch failed")); + } sketchshape.move(invObjLoc); - TopoShape prism(0,getDocument()->getStringHasher()); + TopoShape prism(0); if (method == "UpToFirst" || method == "UpToLast" || method == "UpToFace") { // Note: This will return an unlimited planar face if support is a datum plane - TopoShape supportface = getSupportFace(); + TopoShape supportface = getTopoShapeSupportFace(); supportface.move(invObjLoc); - if (Reversed.getValue()) + if (Reversed.getValue()) { dir.Reverse(); + } // Find a valid face or datum plane to extrude up to TopoShape upToFace; @@ -544,13 +574,18 @@ App::DocumentObjectExecReturn *FeatureExtrude::buildExtrusion(ExtrudeOptions opt getUpToFace(upToFace, base, supportface, sketchshape, method, dir); addOffsetToFace(upToFace, dir, Offset.getValue()); - if (!supportface.hasSubShape(TopAbs_WIRE)) + if (!supportface.hasSubShape(TopAbs_WIRE)) { supportface = TopoShape(); + } if (legacyPocket) { - auto mode = base.isNull() ? TopoShape::PrismMode::None - : TopoShape::PrismMode::CutFromBase; - prism = base.makeElementPrismUntil(sketchshape, supportface, upToFace, - dir, mode, false/*CheckUpToFaceLimits.getValue()*/); + auto mode = + base.isNull() ? TopoShape::PrismMode::None : TopoShape::PrismMode::CutFromBase; + prism = base.makeElementPrismUntil(sketchshape, + supportface, + upToFace, + dir, + mode, + false /*CheckUpToFaceLimits.getValue()*/); // DO NOT assign id to the generated prism, because this prism is // actually the final result. We obtain the subtracted shape by cut // this prism with the original base. Assigning a minus self id here @@ -561,59 +596,88 @@ App::DocumentObjectExecReturn *FeatureExtrude::buildExtrusion(ExtrudeOptions opt // And the really expensive way to get the SubShape... try { - TopoShape result(0,getDocument()->getStringHasher()); - if (base.isNull()) + TopoShape result(0); + if (base.isNull()) { result = prism; - else - result.makeElementCut({base,prism}); + } + else { + result.makeElementCut({base, prism}); + } result = refineShapeIfActive(result); this->AddSubShape.setValue(result); - }catch(Standard_Failure &) { - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Up to face: Could not get SubShape!")); + } + catch (Standard_Failure&) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Up to face: Could not get SubShape!")); } - if (getAddSubType() == Additive) + if (getAddSubType() == Additive) { prism = base.makeElementFuse(this->AddSubShape.getShape()); - else + } + else { prism = refineShapeIfActive(prism); + } this->Shape.setValue(getSolid(prism)); return App::DocumentObject::StdReturn; } - prism.makeElementPrismUntil(base, sketchshape, supportface, upToFace, - dir, TopoShape::PrismMode::None, false /*CheckUpToFaceLimits.getValue()*/); - } else { + prism.makeElementPrismUntil(base, + sketchshape, + supportface, + upToFace, + dir, + TopoShape::PrismMode::None, + false /*CheckUpToFaceLimits.getValue()*/); + } + else { Part::ExtrusionParameters params; params.dir = dir; params.solid = makeface; params.taperAngleFwd = this->TaperAngle.getValue() * M_PI / 180.0; params.taperAngleRev = this->TaperAngle2.getValue() * M_PI / 180.0; if (L2 == 0.0 && Midplane.getValue()) { - params.lengthFwd = L/2; - params.lengthRev = L/2; - if (params.taperAngleRev == 0.0) + params.lengthFwd = L / 2; + params.lengthRev = L / 2; + if (params.taperAngleRev == 0.0) { params.taperAngleRev = params.taperAngleFwd; - } else { + } + } + else { params.lengthFwd = L; params.lengthRev = L2; } if (std::fabs(params.taperAngleFwd) >= Precision::Angular() - || std::fabs(params.taperAngleRev) >= Precision::Angular() ) { + || std::fabs(params.taperAngleRev) >= Precision::Angular()) { if (fabs(params.taperAngleFwd) > M_PI * 0.5 - Precision::Angular() - || fabs(params.taperAngleRev) > M_PI * 0.5 - Precision::Angular()) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", - "Magnitude of taper angle matches or exceeds 90 degrees")); - if (Reversed.getValue()) + || fabs(params.taperAngleRev) > M_PI * 0.5 - Precision::Angular()) { + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP( + "Exception", + "Magnitude of taper angle matches or exceeds 90 degrees")); + } + if (Reversed.getValue()) { params.dir.Reverse(); + } std::vector drafts; Part::ExtrusionHelper::makeElementDraft(params, sketchshape, drafts); - if (drafts.empty()) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Padding with draft angle failed")); - prism.makeElementCompound(drafts, nullptr, TopoShape::SingleShapeCompoundCreationPolicy::returnShape); - - } else - generatePrism(prism, sketchshape, method, dir, L, L2, - Midplane.getValue(), Reversed.getValue()); + if (drafts.empty()) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Padding with draft angle failed")); + } + prism.makeElementCompound( + drafts, + nullptr, + TopoShape::SingleShapeCompoundCreationPolicy::returnShape); + } + else { + generatePrism(prism, + sketchshape, + method, + dir, + L, + L2, + Midplane.getValue(), + Reversed.getValue()); + } } // set the additive shape property for later usage in e.g. pattern @@ -624,9 +688,9 @@ App::DocumentObjectExecReturn *FeatureExtrude::buildExtrusion(ExtrudeOptions opt prism.Tag = -this->getID(); // Let's call algorithm computing a fuse operation: - TopoShape result(0,getDocument()->getStringHasher()); + TopoShape result(0); try { - const char *maker; + const char* maker; switch (getAddSubType()) { case Subtractive: maker = Part::OpCodes::Cut; @@ -634,26 +698,31 @@ App::DocumentObjectExecReturn *FeatureExtrude::buildExtrusion(ExtrudeOptions opt default: maker = Part::OpCodes::Fuse; } - result.makeElementBoolean(maker, {base,prism}); - }catch(Standard_Failure &){ - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", - "Fusion with base feature failed")); + result.makeElementBoolean(maker, {base, prism}); + } + catch (Standard_Failure&) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Fusion with base feature failed")); } // we have to get the solids (fuse sometimes creates compounds) auto solRes = this->getSolid(result); // lets check if the result is a solid - if (solRes.isNull()) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", - "Resulting shape is not a solid")); + if (solRes.isNull()) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Resulting shape is not a solid")); + } solRes = refineShapeIfActive(solRes); this->Shape.setValue(getSolid(solRes)); - } else if (prism.hasSubShape(TopAbs_SOLID)) { - if (prism.countSubShapes(TopAbs_SOLID) > 1) + } + else if (prism.hasSubShape(TopAbs_SOLID)) { + if (prism.countSubShapes(TopAbs_SOLID) > 1) { prism.makeElementFuse(prism.getSubTopoShapes(TopAbs_SOLID)); + } prism = refineShapeIfActive(prism); this->Shape.setValue(getSolid(prism)); - } else { + } + else { prism = refineShapeIfActive(prism); this->Shape.setValue(prism); } @@ -664,15 +733,17 @@ App::DocumentObjectExecReturn *FeatureExtrude::buildExtrusion(ExtrudeOptions opt return App::DocumentObject::StdReturn; } catch (Standard_Failure& e) { - if (std::string(e.GetMessageString()) == "TopoDS::Face") - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", - "Could not create face from sketch.\n" - "Intersecting sketch entities or multiple faces in a sketch are not allowed.")); - else + if (std::string(e.GetMessageString()) == "TopoDS::Face") { + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP( + "Exception", + "Could not create face from sketch.\n" + "Intersecting sketch entities or multiple faces in a sketch are not allowed.")); + } + else { return new App::DocumentObjectExecReturn(e.GetMessageString()); + } } catch (Base::Exception& e) { return new App::DocumentObjectExecReturn(e.what()); } - } diff --git a/src/Mod/PartDesign/App/FeatureExtrude.h b/src/Mod/PartDesign/App/FeatureExtrude.h index 9ba2945ee8..22ed2741e6 100644 --- a/src/Mod/PartDesign/App/FeatureExtrude.h +++ b/src/Mod/PartDesign/App/FeatureExtrude.h @@ -68,7 +68,8 @@ protected: bool hasTaperedAngle() const; /// Options for buildExtrusion() - enum class ExtrudeOption { + enum class ExtrudeOption + { MakeFace = 1, MakeFuse = 2, LegacyPocket = 4, @@ -77,7 +78,7 @@ protected: using ExtrudeOptions = Base::Flags; - App::DocumentObjectExecReturn *buildExtrusion(ExtrudeOptions options); + App::DocumentObjectExecReturn* buildExtrusion(ExtrudeOptions options); /** * Generates an extrusion of the input sketchshape and stores it in the given \a prism diff --git a/src/Mod/PartDesign/App/FeaturePad.cpp b/src/Mod/PartDesign/App/FeaturePad.cpp index 3e22e81077..cab6b2f754 100644 --- a/src/Mod/PartDesign/App/FeaturePad.cpp +++ b/src/Mod/PartDesign/App/FeaturePad.cpp @@ -67,6 +67,13 @@ Pad::Pad() Length2.setConstraints(nullptr); } +#ifdef FC_USE_TNP_FIX + +App::DocumentObjectExecReturn* Pad::execute() +{ + return buildExtrusion(ExtrudeOption::MakeFace | ExtrudeOption::MakeFuse); +} +#else App::DocumentObjectExecReturn *Pad::execute() { double L = Length.getValue(); @@ -262,3 +269,4 @@ App::DocumentObjectExecReturn *Pad::execute() } } +#endif diff --git a/src/Mod/PartDesign/App/FeaturePrimitive.cpp b/src/Mod/PartDesign/App/FeaturePrimitive.cpp index 1e8b743a8f..47b9ffe277 100644 --- a/src/Mod/PartDesign/App/FeaturePrimitive.cpp +++ b/src/Mod/PartDesign/App/FeaturePrimitive.cpp @@ -45,6 +45,7 @@ #include "FeaturePrimitive.h" #include "FeaturePy.h" +#include "Mod/Part/App/TopoShapeOpCode.h" using namespace PartDesign; @@ -65,19 +66,34 @@ FeaturePrimitive::FeaturePrimitive() Part::AttachExtension::initExtension(this); } -App::DocumentObjectExecReturn* FeaturePrimitive::execute(const TopoDS_Shape& primitiveShape) +App::DocumentObjectExecReturn* FeaturePrimitive::execute(const TopoDS_Shape& primitive) { try { //transform the primitive in the correct coordinance FeatureAddSub::execute(); //if we have no base we just add the standard primitive shape +#ifdef FC_USE_TNP_FIX + TopoShape primitiveShape; + primitiveShape.setShape(primitive); + + TopoShape base; + try { + // if we have a base shape we need to make sure that it does not get our transformation + // to + base = getBaseTopoShape().moved(getLocation().Inverted()); + primitiveShape.Tag = -this->getID(); + } + +#else + auto primitiveShape = primitive; TopoDS_Shape base; try { //if we have a base shape we need to make sure that it does not get our transformation to BRepBuilderAPI_Transform trsf(getBaseShape(), getLocation().Transformation().Inverted(), true); base = trsf.Shape(); } +#endif catch (const Base::Exception&) { //as we use this for preview we can add it even if useless for subtractive @@ -90,14 +106,45 @@ App::DocumentObjectExecReturn* FeaturePrimitive::execute(const TopoDS_Shape& pri return App::DocumentObject::StdReturn; } +#ifdef FC_USE_TNP_FIX + AddSubShape.setValue(primitiveShape); + TopoShape boolOp(0); + + const char* maker; + switch (getAddSubType()) { + case Additive: + maker = Part::OpCodes::Fuse; + break; + case Subtractive: + maker = Part::OpCodes::Cut; + break; + default: + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Unknown operation type")); + } + try { + boolOp.makeElementBoolean(maker, {base, primitiveShape}); + } + catch (Standard_Failure& e) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Failed to perform boolean operation")); + } + boolOp = this->getSolid(boolOp); + // lets check if the result is a solid + if (boolOp.isNull()) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Resulting shape is not a solid")); + } +#else + TopoDS_Shape boolOp; if (getAddSubType() == FeatureAddSub::Additive) { BRepAlgoAPI_Fuse mkFuse(base, primitiveShape); if (!mkFuse.IsDone()) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Adding the primitive failed")); // we have to get the solids (fuse sometimes creates compounds) - TopoDS_Shape boolOp = this->getSolid(mkFuse.Shape()); + boolOp = this->getSolid(mkFuse.Shape()); // lets check if the result is a solid if (boolOp.IsNull()) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Resulting shape is not a solid")); @@ -106,10 +153,6 @@ App::DocumentObjectExecReturn* FeaturePrimitive::execute(const TopoDS_Shape& pri if (solidCount > 1) { return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Result has multiple solids: that is not currently supported.")); } - - boolOp = refineShapeIfActive(boolOp); - Shape.setValue(getSolid(boolOp)); - AddSubShape.setValue(primitiveShape); } else if (getAddSubType() == FeatureAddSub::Subtractive) { @@ -117,7 +160,7 @@ App::DocumentObjectExecReturn* FeaturePrimitive::execute(const TopoDS_Shape& pri if (!mkCut.IsDone()) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Subtracting the primitive failed")); // we have to get the solids (fuse sometimes creates compounds) - TopoDS_Shape boolOp = this->getSolid(mkCut.Shape()); + boolOp = this->getSolid(mkCut.Shape()); // lets check if the result is a solid if (boolOp.IsNull()) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Resulting shape is not a solid")); @@ -126,13 +169,11 @@ App::DocumentObjectExecReturn* FeaturePrimitive::execute(const TopoDS_Shape& pri if (solidCount > 1) { return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Result has multiple solids: that is not currently supported.")); } - - boolOp = refineShapeIfActive(boolOp); - Shape.setValue(getSolid(boolOp)); - AddSubShape.setValue(primitiveShape); } - - +#endif + boolOp = refineShapeIfActive(boolOp); + Shape.setValue(getSolid(boolOp)); + AddSubShape.setValue(primitiveShape); } catch (Standard_Failure& e) { diff --git a/src/Mod/PartDesign/App/FeatureSketchBased.cpp b/src/Mod/PartDesign/App/FeatureSketchBased.cpp index b89d14e5c7..1673d65e45 100644 --- a/src/Mod/PartDesign/App/FeatureSketchBased.cpp +++ b/src/Mod/PartDesign/App/FeatureSketchBased.cpp @@ -165,6 +165,29 @@ Part::Feature* ProfileBased::getVerifiedObject(bool silent) const { return static_cast(result); } +#ifdef FC_USE_TNP_FIX +TopoShape ProfileBased::getProfileShape() const +{ + TopoShape shape; + const auto& subs = Profile.getSubValues(); + auto profile = Profile.getValue(); + if (subs.empty()) { + shape = Part::Feature::getTopoShape(profile); + } + else { + std::vector shapes; + for (auto& sub : subs) { + shapes.push_back( + Part::Feature::getTopoShape(profile, sub.c_str(), /* needSubElement */ true)); + } + shape = TopoShape(shape.Tag).makeElementCompound(shapes); + } + if (shape.isNull()) { + throw Part::NullShapeException("Linked shape object is empty"); + } + return shape; +} +#else Part::TopoShape ProfileBased::getProfileShape() const { auto shape = getTopoShape(Profile.getValue()); @@ -176,7 +199,8 @@ Part::TopoShape ProfileBased::getProfileShape() const } return shape; } - +#endif +// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. TopoDS_Shape ProfileBased::getVerifiedFace(bool silent) const { App::DocumentObject* result = Profile.getValue(); @@ -256,134 +280,166 @@ TopoDS_Shape ProfileBased::getVerifiedFace(bool silent) const { } TopoShape ProfileBased::getTopoShapeVerifiedFace(bool silent, - bool doFit, - bool allowOpen, - const App::DocumentObject *profile, - const std::vector &_subs) const + bool doFit, + bool allowOpen, + const App::DocumentObject* profile, + const std::vector& _subs) const { auto obj = profile ? profile : Profile.getValue(); - if(!obj || !obj->getNameInDocument()) { - if(silent) + if (!obj || !obj->getNameInDocument()) { + if (silent) { return TopoShape(); + } throw Base::ValueError("No profile linked"); } - const auto &subs = profile ? _subs : Profile.getSubValues(); + const auto& subs = profile ? _subs : Profile.getSubValues(); try { TopoShape shape; - if(AllowMultiFace.getValue()) { - if (subs.empty()) + if (AllowMultiFace.getValue()) { + if (subs.empty()) { shape = Part::Feature::getTopoShape(obj); + } else { std::vector shapes; - for (auto &sub : subs) { - auto subshape = Part::Feature::getTopoShape( - obj, sub.c_str(), /*needSubElement*/true); - if (subshape.isNull()) - FC_THROWM(Base::CADKernelError, "Sub shape not found: " << - obj->getFullName() << "." << sub); + for (auto& sub : subs) { + auto subshape = + Part::Feature::getTopoShape(obj, sub.c_str(), /*needSubElement*/ true); + if (subshape.isNull()) { + FC_THROWM(Base::CADKernelError, + "Sub shape not found: " << obj->getFullName() << "." << sub); + } shapes.push_back(subshape); } shape.makeElementCompound(shapes); } - } else { - std::string sub; - if(!obj->getTypeId().isDerivedFrom(Part::Part2DObject::getClassTypeId())) { - if(!subs.empty()) - sub = subs[0]; - } - shape = Part::Feature::getTopoShape(obj,sub.c_str(),!sub.empty()); } - if(shape.isNull()) { - if (silent) + else { + std::string sub; + if (!obj->getTypeId().isDerivedFrom(Part::Part2DObject::getClassTypeId())) { + if (!subs.empty()) { + sub = subs[0]; + } + } + shape = Part::Feature::getTopoShape(obj, sub.c_str(), !sub.empty()); + } + if (shape.isNull()) { + if (silent) { return shape; + } throw Base::CADKernelError("Linked shape object is empty"); } TopoShape openshape; - if(!shape.hasSubShape(TopAbs_FACE)) { + if (!shape.hasSubShape(TopAbs_FACE)) { try { - if(!shape.hasSubShape(TopAbs_WIRE)) + if (!shape.hasSubShape(TopAbs_WIRE)) { shape = shape.makeElementWires(); - if(shape.hasSubShape(TopAbs_WIRE)) { + } + if (shape.hasSubShape(TopAbs_WIRE)) { shape.Hasher = getDocument()->getStringHasher(); if (allowOpen) { std::vector openwires; std::vector wires; - for (auto &wire : shape.getSubTopoShapes(TopAbs_WIRE)) { - if (!wire.isClosed()) + for (auto& wire : shape.getSubTopoShapes(TopAbs_WIRE)) { + if (!wire.isClosed()) { openwires.push_back(wire); - else + } + else { wires.push_back(wire); + } } if (openwires.size()) { - openshape.makeElementCompound(openwires, nullptr, TopoShape ::SingleShapeCompoundCreationPolicy::returnShape); - if (wires.empty()) + openshape.makeElementCompound( + openwires, + nullptr, + TopoShape ::SingleShapeCompoundCreationPolicy::returnShape); + if (wires.empty()) { shape = TopoShape(); - else - shape.makeElementCompound(wires, nullptr, TopoShape ::SingleShapeCompoundCreationPolicy::returnShape); + } + else { + shape.makeElementCompound( + wires, + nullptr, + TopoShape ::SingleShapeCompoundCreationPolicy::returnShape); + } } } if (!shape.isNull()) { - if (AllowMultiFace.getValue()) - shape = shape.makeElementFace(); // default to use FaceMakerBullseye - else + if (AllowMultiFace.getValue()) { + shape = shape.makeElementFace(); // default to use FaceMakerBullseye + } + else { shape = shape.makeElementFace(nullptr, "Part::FaceMakerCheese"); + } } } - } catch (const Base::Exception &) { - if (silent) + } + catch (const Base::Exception&) { + if (silent) { return TopoShape(); + } throw; - } catch (const Standard_Failure &) { - if (silent) + } + catch (const Standard_Failure&) { + if (silent) { return TopoShape(); + } throw; } } int count = shape.countSubShapes(TopAbs_FACE); - if(!count && !allowOpen) { - if(silent) + if (!count && !allowOpen) { + if (silent) { return TopoShape(); + } throw Base::CADKernelError("Cannot make face from profile"); } -// if (doFit && (std::abs(Fit.getValue()) > Precision::Confusion() -// || std::abs(InnerFit.getValue()) > Precision::Confusion())) { -// -// if (!shape.isNull()) -// shape = shape.makEOffsetFace(Fit.getValue(), -// InnerFit.getValue(), -// static_cast(FitJoin.getValue()), -// static_cast(InnerFitJoin.getValue())); -// if (!openshape.isNull()) -// openshape.makEOffset2D(Fit.getValue()); -// } + // Toponaming April 2024: This appears to be new feature, not TNP: + // if (doFit && (std::abs(Fit.getValue()) > Precision::Confusion() + // || std::abs(InnerFit.getValue()) > Precision::Confusion())) { + // + // if (!shape.isNull()) + // shape = shape.makEOffsetFace(Fit.getValue(), + // InnerFit.getValue(), + // static_cast(FitJoin.getValue()), + // static_cast(InnerFitJoin.getValue())); + // if (!openshape.isNull()) + // openshape.makEOffset2D(Fit.getValue()); + // } if (!openshape.isNull()) { - if (shape.isNull()) + if (shape.isNull()) { shape = openshape; - else + } + else { shape.makeElementCompound({shape, openshape}); + } } - if(count>1) { - if(AllowMultiFace.getValue() -// || allowMultiSolid() - || obj->isDerivedFrom(Part::Part2DObject::getClassTypeId())) + if (count > 1) { + if (AllowMultiFace.getValue() + || obj->isDerivedFrom(Part::Part2DObject::getClassTypeId())) { return shape; + } FC_WARN("Found more than one face from profile"); } - if (!openshape.isNull()) + if (!openshape.isNull()) { return shape; - if (count) - return shape.getSubTopoShape(TopAbs_FACE,1); + } + if (count) { + return shape.getSubTopoShape(TopAbs_FACE, 1); + } return shape; - }catch (Standard_Failure &) { - if(silent) + } + catch (Standard_Failure&) { + if (silent) { return TopoShape(); + } throw; } } +// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. std::vector ProfileBased::getProfileWires() const { std::vector result; @@ -422,25 +478,29 @@ std::vector ProfileBased::getProfileWires() const { return result; } -std::vector ProfileBased::getTopoShapeProfileWires() const { +std::vector ProfileBased::getTopoShapeProfileWires() const +{ // shape copy is a workaround for an obscure OCC bug which leads to empty // tessellations for some faces. Making an explicit copy of the linked // shape seems to fix it. The error mostly happens when re-computing the // shape but sometimes also for the first time auto shape = getProfileShape().makeElementCopy(); - if(shape.hasSubShape(TopAbs_WIRE)) + if (shape.hasSubShape(TopAbs_WIRE)) { return shape.getSubTopoShapes(TopAbs_WIRE); + } auto wires = shape.makeElementWires().getSubTopoShapes(TopAbs_WIRE); - if(wires.empty()) + if (wires.empty()) { throw Part::NullShapeException("Linked shape object is not a wire"); + } return wires; } // Note: We cannot return a reference, because it will become Null. // Not clear where, because we check for IsNull() here, but as soon as it is passed out of // this method, it becomes null! +// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. const TopoDS_Face ProfileBased::getSupportFace() const { const Part::Part2DObject* sketch = getVerifiedSketch(true); @@ -453,7 +513,8 @@ const TopoDS_Face ProfileBased::getSupportFace() const TopoDS_Face ProfileBased::getSupportFace(const Part::Part2DObject* sketch) const { - if (sketch && sketch->MapMode.getValue() == Attacher::mmFlatFace && sketch->AttachmentSupport.getValue()) { + if (sketch && sketch->MapMode.getValue() == Attacher::mmFlatFace + && sketch->AttachmentSupport.getValue()) { const auto& AttachmentSupport = sketch->AttachmentSupport; App::DocumentObject* ref = AttachmentSupport.getValue(); @@ -469,20 +530,24 @@ TopoDS_Face ProfileBased::getSupportFace(const Part::Part2DObject* sketch) const // get the selected sub shape (a Face) const Part::TopoShape& shape = part->Shape.getShape(); - if (shape.getShape().IsNull()) + if (shape.getShape().IsNull()) { throw Base::ValueError("Sketch support shape is empty!"); + } TopoDS_Shape sh = shape.getSubShape(sub[0].c_str()); - if (sh.IsNull()) + if (sh.IsNull()) { throw Base::ValueError("Null shape in SketchBased::getSupportFace()!"); + } const TopoDS_Face face = TopoDS::Face(sh); - if (face.IsNull()) + if (face.IsNull()) { throw Base::ValueError("Null face in SketchBased::getSupportFace()!"); + } BRepAdaptor_Surface adapt(face); - if (adapt.GetType() != GeomAbs_Plane) + if (adapt.GetType() != GeomAbs_Plane) { throw Base::TypeError("No planar face in SketchBased::getSupportFace()!"); + } return face; } @@ -502,31 +567,39 @@ TopoDS_Face ProfileBased::getSupportFace(const App::PropertyLinkSub& link) const return face; } -TopoShape ProfileBased::getTopoShapeSupportFace() const { +TopoShape ProfileBased::getTopoShapeSupportFace() const +{ TopoShape shape; const Part::Part2DObject* sketch = getVerifiedSketch(true); - if (!sketch) - shape = getVerifiedFace(); - else if (sketch->MapMode.getValue() == Attacher::mmFlatFace && sketch->AttachmentSupport.getValue()) { - const auto &Support = sketch->AttachmentSupport; + if (!sketch) { + shape = getTopoShapeVerifiedFace(); + } + else if (sketch->MapMode.getValue() == Attacher::mmFlatFace + && sketch->AttachmentSupport.getValue()) { + const auto& Support = sketch->AttachmentSupport; App::DocumentObject* ref = Support.getValue(); shape = Part::Feature::getTopoShape( - ref, Support.getSubValues().size() ? Support.getSubValues()[0].c_str() : "", true); + ref, + Support.getSubValues().size() ? Support.getSubValues()[0].c_str() : "", + true); } if (!shape.isNull()) { if (shape.shapeType(true) != TopAbs_FACE) { - if (!shape.hasSubShape(TopAbs_FACE)) + if (!shape.hasSubShape(TopAbs_FACE)) { throw Base::ValueError("Null face in SketchBased::getSupportFace()!"); + } shape = shape.getSubTopoShape(TopAbs_FACE, 1); } gp_Pln pln; - if (!shape.findPlane(pln)) + if (!shape.findPlane(pln)) { throw Base::TypeError("No planar face in SketchBased::getSupportFace()!"); + } return shape; } - if (!sketch) + if (!sketch) { throw Base::RuntimeError("No planar support"); + } return Feature::makeShapeFromPlane(sketch); } @@ -589,6 +662,26 @@ void ProfileBased::onChanged(const App::Property* prop) FeatureAddSub::onChanged(prop); } +void ProfileBased::getUpToFaceFromLinkSub(TopoShape& upToFace, const App::PropertyLinkSub& refFace) +{ + App::DocumentObject* ref = refFace.getValue(); + + if (!ref) { + throw Base::ValueError("SketchBased: No face selected"); + } + + if (ref->getTypeId().isDerivedFrom(App::Plane::getClassTypeId())) { + upToFace = makeShapeFromPlane(ref); + return; + } + + const auto& subs = refFace.getSubValues(); + upToFace = Part::Feature::getTopoShape(ref, subs.size() ? subs[0].c_str() : nullptr, true); + if (!upToFace.hasSubShape(TopAbs_FACE)) { + throw Base::ValueError("SketchBased: Up to face: Failed to extract face"); + } +} + void ProfileBased::getFaceFromLinkSub(TopoDS_Face& upToFace, const App::PropertyLinkSub& refFace) { @@ -621,6 +714,7 @@ void ProfileBased::getFaceFromLinkSub(TopoDS_Face& upToFace, const App::Property throw Base::ValueError("SketchBased: Failed to extract face"); } +// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. void ProfileBased::getUpToFace(TopoDS_Face& upToFace, const TopoDS_Shape& support, const TopoDS_Shape& sketchshape, @@ -722,46 +816,59 @@ void ProfileBased::getUpToFace(TopoShape& upToFace, gp_Dir& dir) { if ((method == "UpToLast") || (method == "UpToFirst")) { - std::vector cfaces = Part::findAllFacesCutBy(support, sketchshape, dir); - if (cfaces.empty()) + std::vector cfaces = + Part::findAllFacesCutBy(support, sketchshape, dir); + if (cfaces.empty()) { throw Base::ValueError("SketchBased: No faces found in this direction"); + } // Find nearest/furthest face - std::vector::const_iterator it, it_near, it_far; + std::vector::const_iterator it, it_near, it_far; it_near = it_far = cfaces.begin(); - for (it = cfaces.begin(); it != cfaces.end(); it++) - if (it->distsq > it_far->distsq) + for (it = cfaces.begin(); it != cfaces.end(); it++) { + if (it->distsq > it_far->distsq) { it_far = it; - else if (it->distsq < it_near->distsq) + } + else if (it->distsq < it_near->distsq) { it_near = it; + } + } upToFace = (method == "UpToLast" ? it_far->face : it_near->face); - } else if (Part::findAllFacesCutBy(upToFace, sketchshape, dir).empty()) + } + else if (Part::findAllFacesCutBy(upToFace, sketchshape, dir).empty()) { dir = -dir; + } if (upToFace.shapeType(true) != TopAbs_FACE) { - if (!upToFace.hasSubShape(TopAbs_FACE)) + if (!upToFace.hasSubShape(TopAbs_FACE)) { throw Base::ValueError("SketchBased: Up to face: No face found"); + } upToFace = upToFace.getSubTopoShape(TopAbs_FACE, 1); } TopoDS_Face face = TopoDS::Face(upToFace.getShape()); // Check that the upToFace does not intersect the sketch face and - // is not parallel to the extrusion direction (for simplicity, supportface is used instead of sketchshape) + // is not parallel to the extrusion direction (for simplicity, supportface is used instead of + // sketchshape) BRepAdaptor_Surface adapt1(TopoDS::Face(supportface.getShape())); BRepAdaptor_Surface adapt2(face); if (adapt2.GetType() == GeomAbs_Plane) { - if (adapt1.Plane().Axis().IsNormal(adapt2.Plane().Axis(), Precision::Confusion())) - throw Base::ValueError("SketchBased: Up to face: Must not be parallel to extrusion direction!"); + if (adapt1.Plane().Axis().IsNormal(adapt2.Plane().Axis(), Precision::Confusion())) { + throw Base::ValueError( + "SketchBased: Up to face: Must not be parallel to extrusion direction!"); + } } // We must measure from sketchshape, not supportface, here BRepExtrema_DistShapeShape distSS(sketchshape.getShape(), face); - if (distSS.Value() < Precision::Confusion()) + if (distSS.Value() < Precision::Confusion()) { throw Base::ValueError("SketchBased: Up to face: Must not intersect sketch!"); + } } +// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. void ProfileBased::addOffsetToFace(TopoDS_Face& upToFace, const gp_Dir& dir, double offset) { // Move the face in the extrusion direction diff --git a/src/Mod/PartDesign/App/FeatureSketchBased.h b/src/Mod/PartDesign/App/FeatureSketchBased.h index be4204d334..4db8246c6c 100644 --- a/src/Mod/PartDesign/App/FeatureSketchBased.h +++ b/src/Mod/PartDesign/App/FeatureSketchBased.h @@ -97,6 +97,7 @@ public: * silently returns nullptr, otherwise throw a Base::Exception. * Default is false. */ + // TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. TopoDS_Shape getVerifiedFace(bool silent = false) const; /** @@ -110,18 +111,20 @@ public: * @param subs: optional profile sub-object names, if not given then use 'Profile' property */ TopoShape getTopoShapeVerifiedFace(bool silent = false, - bool doFit = true, - bool allowOpen = false, - const App::DocumentObject *profile = nullptr, - const std::vector &subs = {}) const; + bool doFit = true, + bool allowOpen = false, + const App::DocumentObject* profile = nullptr, + const std::vector& subs = {}) const; /// Returns the wires the sketch is composed of + // TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. std::vector getProfileWires() const; std::vector getTopoShapeProfileWires() const; /// Returns the face of the sketch support (if any) const TopoDS_Face getSupportFace() const; + // TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. TopoShape getTopoShapeSupportFace() const; Base::Vector3d getProfileNormal() const; @@ -147,8 +150,7 @@ protected: TopoDS_Face getSupportFace(const App::PropertyLinkSub& link) const; /// Extract a face from a given LinkSub - static void getFaceFromLinkSub(TopoDS_Face& upToFace, - const App::PropertyLinkSub& refFace); + static void getFaceFromLinkSub(TopoDS_Face& upToFace, const App::PropertyLinkSub& refFace); /// Find a valid face to extrude up to static void getUpToFace(TopoDS_Face& upToFace, @@ -158,12 +160,9 @@ protected: const gp_Dir& dir); /// Add an offset to the face - static void addOffsetToFace(TopoDS_Face& upToFace, - const gp_Dir& dir, - double offset); + static void addOffsetToFace(TopoDS_Face& upToFace, const gp_Dir& dir, double offset); /// Extract a face from a given LinkSub - static void getUpToFaceFromLinkSub(TopoShape& upToFace, - const App::PropertyLinkSub& refFace); + static void getUpToFaceFromLinkSub(TopoShape& upToFace, const App::PropertyLinkSub& refFace); /// Find a valid face to extrude up to static void getUpToFace(TopoShape& upToFace, diff --git a/src/Mod/Sketcher/App/SketchObject.cpp b/src/Mod/Sketcher/App/SketchObject.cpp index 2d1e1b7641..4ebe85e5c3 100644 --- a/src/Mod/Sketcher/App/SketchObject.cpp +++ b/src/Mod/Sketcher/App/SketchObject.cpp @@ -261,6 +261,20 @@ void SketchObject::buildShape() if(GeometryFacade::getConstruction(geo)) continue; if (geo->isDerivedFrom(Part::GeomPoint::getClassTypeId())) { +#ifdef FC_USE_TNP_FIX + Part::TopoShape vertex(TopoDS::Vertex(geo->toShape())); + int idx = getVertexIndexGeoPos(i-1, Sketcher::PointPos::start); + std::string name = convertSubName(Data::IndexedName::fromConst("Vertex", idx+1), false); + vertex.setElementName(Data::IndexedName::fromConst("Vertex", 1), + Data::MappedName::fromRawData(name.c_str()),0L); + vertices.push_back(vertex); + vertices.back().copyElementMap(vertex, Part::OpCodes::Sketch); + } else { + auto indexedName = Data::IndexedName::fromConst("Edge", i); + shapes.push_back(getEdge(geo,convertSubName(indexedName, false).c_str())); + } + +#else vertices.emplace_back(TopoDS::Vertex(geo->toShape())); int idx = getVertexIndexGeoPos(i-1, PointPos::start); std::string name = convertSubName(Data::IndexedName::fromConst("Vertex", idx+1), false); @@ -269,6 +283,7 @@ void SketchObject::buildShape() } else shapes.push_back(getEdge(geo,convertSubName( Data::IndexedName::fromConst("Edge", i), false).c_str())); +#endif } // FIXME: Commented since ExternalGeometryFacade is not added @@ -280,11 +295,14 @@ void SketchObject::buildShape() // shapes.push_back(getEdge(geo, convertSubName( // Data::IndexedName::fromConst("ExternalEdge", i-1), false).c_str())); // } - if(shapes.empty() && vertices.empty()) - Shape.setValue(Part::TopoShape()); - else if (vertices.empty()) { + if(shapes.empty() && vertices.empty()) { + Shape.setValue(Part::TopoShape()); + return; + } + Part::TopoShape result(0); + if (vertices.empty()) { // Notice here we supply op code Part::OpCodes::Sketch to makEWires(). - Shape.setValue(Part::TopoShape().makeElementWires(shapes,Part::OpCodes::Sketch)); + result.makeElementWires(shapes,Part::OpCodes::Sketch); } else { std::vector results; if (!shapes.empty()) { @@ -302,8 +320,10 @@ void SketchObject::buildShape() results.push_back(wire); } results.insert(results.end(), vertices.begin(), vertices.end()); - Shape.setValue(Part::TopoShape().makeElementCompound(results, Part::OpCodes::Sketch)); + result.makeElementCompound(results, Part::OpCodes::Sketch); } + result.Tag = getID(); + Shape.setValue(result); } static const char *hasSketchMarker(const char *name) { diff --git a/tests/src/Mod/Part/App/PartTestHelpers.cpp b/tests/src/Mod/Part/App/PartTestHelpers.cpp index c9361b9f4b..4c65b746a5 100644 --- a/tests/src/Mod/Part/App/PartTestHelpers.cpp +++ b/tests/src/Mod/Part/App/PartTestHelpers.cpp @@ -160,8 +160,9 @@ bool matchStringsWithoutClause(std::string first, std::string second, const std: /** * Check to see if the elementMap in a shape contains all the names in a list - * The "Duplicate" clause in a name - ";Dnnn" can contain a random number, so we need to - * exclude those. + * There are some sections of the name that can vary due to random numbers or + * memory addresses, so we use a regex to exclude those sections while still + * validating that the name exists and is the correct type. * @param shape The Shape * @param names The vector of names * @return An assertion usable by the gtest framework @@ -178,14 +179,16 @@ testing::AssertionResult elementsMatch(const TopoShape& shape, return matchStringsWithoutClause( element.name.toString(), name, - "(;D|;:H|;K)-?[a-fA-F0-9]+(:[0-9]+)?"); + "(;D|;:H|;K)-?[a-fA-F0-9]+(:[0-9]+)?|(\\(.*?\\))?"); // ;D ;:H and ;K are the sections of an encoded name for // Duplicate, Tag and a Face name in slices. All three of these // can vary from run to run or platform to platform, as they are // based on either explicit random numbers or memory addresses. // Thus we remove the value from comparisons and just check that // they exist. The full form could be something like ;:He59:53 - // which is what we match and remove. + // which is what we match and remove. We also pull out any + // subexpressions wrapped in parens to keep the parse from + // becoming too complex. }) == elements.end()) { return testing::AssertionFailure() << mappedElementVectorToString(elements); diff --git a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp index 3e4516624f..b80873bf0d 100644 --- a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -2381,38 +2381,30 @@ TEST_F(TopoShapeExpansionTest, makeElementPrism) ); } -// TODO: This code was written in Feb 2024 as part of the toponaming project, but appears to be -// unused. It is potentially useful if debugged. -// -// TEST_F(TopoShapeExpansionTest, makeElementPrismUntil) -//{ -// // Arrange -// auto [cube1, cube2] = CreateTwoCubes(); -// TopoShape cube1TS {cube1, 1L}; -// auto subFaces = cube1TS.getSubShapes(TopAbs_FACE); -// auto subTopoFaces = cube1TS.getSubTopoShapes(TopAbs_FACE); -// subTopoFaces[0].Tag = 2L; -// subTopoFaces[1].Tag = 3L; -// auto tr {gp_Trsf()}; -// auto direction = gp_Vec(gp_XYZ(0.0, 0.0, 0.25)); -// tr.SetTranslation(direction); -// auto support = subFaces[0].Moved(TopLoc_Location(tr)); -// auto upto = support.Moved(TopLoc_Location(tr)); -// // Act -// TopoShape result = cube1TS.makeElementPrismUntil(subTopoFaces[0], -// TopoShape(support, 4L), -// TopoShape(upto, 5L), -// direction, -// TopoShape::PrismMode::CutFromBase); -// auto elements = elementMap(result); -// Base::BoundBox3d bb = result.getBoundBox(); -// // Assert shape is correct -// EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(0.0, -0.5, 0.0, 1.5, 1.0, 1.0))); -// EXPECT_FLOAT_EQ(getVolume(result.getShape()), 2); -// // Assert elementMap is correct -// EXPECT_TRUE(elementsMatch(result, -// {"Edge1;:G;XTR;:H2:7,F",})); -//} +TEST_F(TopoShapeExpansionTest, makeElementPrismUntil) +{ + // Arrange + auto [cube1, cube2] = CreateTwoCubes(); + TopoShape cube1TS {cube1, 1L}; + auto subTopoFaces = cube1TS.getSubTopoShapes(TopAbs_FACE); + auto direction = gp_Vec(gp_XYZ(0.0, 0.0, 1)); + // Act + TopoShape result = cube1TS.makeElementPrismUntil(subTopoFaces[4], + subTopoFaces[4], + subTopoFaces[5], + direction, + TopoShape::PrismMode::FuseWithBase); + auto elements = elementMap(result); + Base::BoundBox3d bb = result.getBoundBox(); + // Assert shape is correct + EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(0.0, 0.0, 0.0, 1.0, 1.0, 1.0))); + EXPECT_FLOAT_EQ(getVolume(result.getShape()), 1); + // Assert elementMap is correct + // EXPECT_EQ(result.getElementMapSize(),26); // Todo: Sometimes too big in TNP code. Why? + EXPECT_EQ(result.countSubElements("Edge"), 12); + EXPECT_EQ(result.countSubElements("Face"), 6); + EXPECT_EQ(result.countSubElements("Vertex"), 8); +} TEST_F(TopoShapeExpansionTest, makeElementFilledFace) { From bd307d7cac2067f84c894d9b1729de5862777eb0 Mon Sep 17 00:00:00 2001 From: bgbsww Date: Wed, 10 Apr 2024 12:14:01 -0400 Subject: [PATCH 03/24] Toponaming/Part: Fix review items --- src/Mod/PartDesign/App/Feature.cpp | 25 ++++--------------------- src/Mod/PartDesign/App/Feature.h | 2 +- 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/src/Mod/PartDesign/App/Feature.cpp b/src/Mod/PartDesign/App/Feature.cpp index 28d37c9c45..e61490b9e9 100644 --- a/src/Mod/PartDesign/App/Feature.cpp +++ b/src/Mod/PartDesign/App/Feature.cpp @@ -101,31 +101,14 @@ TopoDS_Shape Feature::getSolid(const TopoDS_Shape& shape) return {}; } -TopoShape Feature::getSolid(const TopoShape& shape, bool force) +TopoShape Feature::getSolid(const TopoShape& shape) { if (shape.isNull()) { throw Part::NullShapeException("Null shape"); } - int count = shape.countSubShapes(TopAbs_SOLID); - if (count > 1) { - if (getFeatureBody()) { - auto res = shape; - res.fixSolidOrientation(); - return res; - } - throw Base::RuntimeError( - "Result has multiple solids.\n" - "To allow multiple solids, please set 'SingleSolid' property of the body to false"); - } - if (count) { - auto res = shape.getSubTopoShape(TopAbs_SOLID, 1); - res.fixSolidOrientation(); - return res; - } - if (force) { - return TopoShape(); - } - return shape; + auto res = shape.getSubTopoShape(TopAbs_SOLID, 1); + res.fixSolidOrientation(); + return res; } int Feature::countSolids(const TopoDS_Shape& shape, TopAbs_ShapeEnum type) diff --git a/src/Mod/PartDesign/App/Feature.h b/src/Mod/PartDesign/App/Feature.h index 8f901f9eeb..8147024f49 100644 --- a/src/Mod/PartDesign/App/Feature.h +++ b/src/Mod/PartDesign/App/Feature.h @@ -96,7 +96,7 @@ protected: */ // TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. static TopoDS_Shape getSolid(const TopoDS_Shape&); - TopoShape getSolid(const TopoShape&, bool force = true); + TopoShape getSolid(const TopoShape&); static int countSolids(const TopoDS_Shape&, TopAbs_ShapeEnum type = TopAbs_SOLID); /// Grab any point from the given face From 6c6dce236d817aa13bd78cdff261f5d200f39ab6 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Thu, 11 Apr 2024 18:20:26 -0400 Subject: [PATCH 04/24] Bring in missing code --- src/Mod/Part/App/TopoShape.cpp | 127 +++++++++++++++++++++- src/Mod/Part/App/TopoShape.h | 2 +- src/Mod/PartDesign/App/FeatureExtrude.cpp | 17 ++- 3 files changed, 137 insertions(+), 9 deletions(-) diff --git a/src/Mod/Part/App/TopoShape.cpp b/src/Mod/Part/App/TopoShape.cpp index b25190f94c..7090a17108 100644 --- a/src/Mod/Part/App/TopoShape.cpp +++ b/src/Mod/Part/App/TopoShape.cpp @@ -3957,8 +3957,130 @@ TopoShape &TopoShape::makeRefine(const TopoShape &shape, const char *op, RefineF return *this; } -// TODO: Does the toponaming branch version of this method need to be here? -bool TopoShape::findPlane(gp_Pln &pln, double tol) const { +#ifdef FC_USE_TNP_FIX +bool TopoShape::findPlane(gp_Pln& pln, double tol, double atol) const +{ + if (_Shape.IsNull()) { + return false; + } + if (tol < 0.0) { + tol = Precision::Confusion(); + } + if (atol < 0.0) { + atol = Precision::Angular(); + } + TopoDS_Shape shape; + if (countSubShapes(TopAbs_EDGE) == 1) { + // To deal with OCCT bug of wrong edge transformation + shape = BRepBuilderAPI_Copy(_Shape).Shape(); + } + else { + shape = _Shape; + } + try { + bool found = false; + // BRepLib_FindSurface only really works on edges. We'll deal face first + for (auto& shape : getSubShapes(TopAbs_FACE)) { + gp_Pln plane; + auto face = TopoDS::Face(shape); + BRepAdaptor_Surface adapt(face); + if (adapt.GetType() == GeomAbs_Plane) { + plane = adapt.Plane(); + } + else { + TopLoc_Location loc; + Handle(Geom_Surface) surf = BRep_Tool::Surface(face, loc); + GeomLib_IsPlanarSurface check(surf); + if (check.IsPlanar()) { + plane = check.Plan(); + } + else { + return false; + } + } + if (!found) { + found = true; + pln = plane; + } + else if (!pln.Position().IsCoplanar(plane.Position(), tol, atol)) { + return false; + } + } + + // Check if there is free edges (i.e. edges does not belong to any face) + if (TopExp_Explorer(getShape(), TopAbs_EDGE, TopAbs_FACE).More()) { + // Copy shape to work around OCC transformation bug, that is, if + // edge has transformation, but underlying geometry does not (or the + // other way round), BRepLib_FindSurface returns a plane with the + // wrong transformation + BRepLib_FindSurface finder(BRepBuilderAPI_Copy(shape).Shape(), tol, Standard_True); + if (!finder.Found()) { + return false; + } + pln = GeomAdaptor_Surface(finder.Surface()).Plane(); + found = true; + } + + // Check for free vertexes + auto vertexes = getSubShapes(TopAbs_VERTEX, TopAbs_EDGE); + if (vertexes.size()) { + if (!found && vertexes.size() > 2) { + BRep_Builder builder; + TopoDS_Compound comp; + builder.MakeCompound(comp); + for (int i = 0, c = (int)vertexes.size() - 1; i < c; ++i) { + builder.Add(comp, + BRepBuilderAPI_MakeEdge(TopoDS::Vertex(vertexes[i]), + TopoDS::Vertex(vertexes[i + 1])) + .Edge()); + } + BRepLib_FindSurface finder(comp, tol, Standard_True); + if (!finder.Found()) { + return false; + } + pln = GeomAdaptor_Surface(finder.Surface()).Plane(); + return true; + } + + double tt = tol * tol; + for (auto& v : vertexes) { + if (pln.SquareDistance(BRep_Tool::Pnt(TopoDS::Vertex(v))) > tt) { + return false; + } + } + } + + // To make the returned plane normal more stable, if the shape has any + // face, use the normal of the first face. + if (hasSubShape(TopAbs_FACE)) { + shape = getSubShape(TopAbs_FACE, 1); + BRepAdaptor_Surface adapt(TopoDS::Face(shape)); + double u = + adapt.FirstUParameter() + (adapt.LastUParameter() - adapt.FirstUParameter()) / 2.; + double v = + adapt.FirstVParameter() + (adapt.LastVParameter() - adapt.FirstVParameter()) / 2.; + BRepLProp_SLProps prop(adapt, u, v, 2, Precision::Confusion()); + if (prop.IsNormalDefined()) { + gp_Pnt pnt; + gp_Vec vec; + // handles the orientation state of the shape + BRepGProp_Face(TopoDS::Face(shape)).Normal(u, v, pnt, vec); + pln = gp_Pln(pnt, gp_Dir(vec)); + } + } + return true; + } + catch (Standard_Failure& e) { + // For some reason the above BRepBuilderAPI_Copy failed to copy + // the geometry of some edge, causing exception with message + // BRepAdaptor_Curve::No geometry. However, without the above + // copy, circular edges often have the wrong transformation! + FC_LOG("failed to find surface: " << e.GetMessageString()); + return false; + } +} +#else +bool TopoShape::findPlane(gp_Pln &pln, double tol, double atol) const { if(_Shape.IsNull()) return false; TopoDS_Shape shape = _Shape; @@ -4004,6 +4126,7 @@ bool TopoShape::findPlane(gp_Pln &pln, double tol) const { return false; } } +#endif bool TopoShape::isInfinite() const { diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index bc4ae8718f..d0e095278f 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -477,7 +477,7 @@ public: bool analyze(bool runBopCheck, std::ostream&) const; bool isClosed() const; bool isCoplanar(const TopoShape& other, double tol = -1) const; - bool findPlane(gp_Pln& plane, double tol = -1) const; + bool findPlane(gp_Pln& plane, double tol = -1, double atol = -1) const; /// Returns true if the expansion of the shape is infinite, false otherwise bool isInfinite() const; /// Checks whether the shape is a planar face diff --git a/src/Mod/PartDesign/App/FeatureExtrude.cpp b/src/Mod/PartDesign/App/FeatureExtrude.cpp index 1cbd35949d..4f7dd026b0 100644 --- a/src/Mod/PartDesign/App/FeatureExtrude.cpp +++ b/src/Mod/PartDesign/App/FeatureExtrude.cpp @@ -1,3 +1,4 @@ + /*************************************************************************** * Copyright (c) 2020 Werner Mayer * * * @@ -567,12 +568,16 @@ App::DocumentObjectExecReturn* FeatureExtrude::buildExtrusion(ExtrudeOptions opt // Find a valid face or datum plane to extrude up to TopoShape upToFace; - if (method == "UpToFace") { - getUpToFaceFromLinkSub(upToFace, UpToFace); - upToFace.move(invObjLoc); + + if (method != "UpToShape") { + if (method == "UpToFace") { + getUpToFaceFromLinkSub(upToFace, UpToFace); + upToFace.move(invObjLoc); + } + getUpToFace(upToFace, base, supportface, sketchshape, method, dir); + addOffsetToFace(upToFace, dir, Offset.getValue()); } - getUpToFace(upToFace, base, supportface, sketchshape, method, dir); - addOffsetToFace(upToFace, dir, Offset.getValue()); + if (!supportface.hasSubShape(TopAbs_WIRE)) { supportface = TopoShape(); @@ -627,7 +632,7 @@ App::DocumentObjectExecReturn* FeatureExtrude::buildExtrusion(ExtrudeOptions opt upToFace, dir, TopoShape::PrismMode::None, - false /*CheckUpToFaceLimits.getValue()*/); + true /*CheckUpToFaceLimits.getValue()*/); } else { Part::ExtrusionParameters params; From afaf0ce8ea2d625e0d624374e1836a874b571714 Mon Sep 17 00:00:00 2001 From: Max Wilfinger <6246609+maxwxyz@users.noreply.github.com> Date: Thu, 11 Apr 2024 21:54:19 +0200 Subject: [PATCH 05/24] Add 'Mesh' option to issue template --- .github/ISSUE_TEMPLATE/PROBLEM_REPORT.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/PROBLEM_REPORT.yml b/.github/ISSUE_TEMPLATE/PROBLEM_REPORT.yml index 813fa74c47..0b721c9621 100644 --- a/.github/ISSUE_TEMPLATE/PROBLEM_REPORT.yml +++ b/.github/ISSUE_TEMPLATE/PROBLEM_REPORT.yml @@ -41,6 +41,7 @@ body: - FEM - File formats - GCS + - Mesh - OpenSCAD - Part - PartDesign From cea3370a248360e71c489356c574b602537c78fc Mon Sep 17 00:00:00 2001 From: wandererfan Date: Wed, 10 Apr 2024 17:28:32 -0400 Subject: [PATCH 06/24] [TD]fix regression of svg hatch during export - last worked in v0.20 - svg hatch tile field is not cropped on export to svg. svg tiles are replaced by pixmap tiles for export. --- src/Mod/TechDraw/Gui/QGIFace.cpp | 235 ++++++++++++++++++++++--------- src/Mod/TechDraw/Gui/QGIFace.h | 85 ++++++----- src/Mod/TechDraw/Gui/QGSPage.cpp | 2 + src/Mod/TechDraw/Gui/QGSPage.h | 3 + 4 files changed, 219 insertions(+), 106 deletions(-) diff --git a/src/Mod/TechDraw/Gui/QGIFace.cpp b/src/Mod/TechDraw/Gui/QGIFace.cpp index 5bf90fbdeb..dcc6850834 100644 --- a/src/Mod/TechDraw/Gui/QGIFace.cpp +++ b/src/Mod/TechDraw/Gui/QGIFace.cpp @@ -43,21 +43,23 @@ #include #include "QGCustomRect.h" #include "QGCustomSvg.h" +#include "QGCustomImage.h" #include "QGICMark.h" #include "QGIPrimPath.h" +#include "QGSPage.h" #include "Rez.h" #include "ZVALUE.h" using namespace TechDrawGui; using namespace TechDraw; +using DU = DrawUtil; + QGIFace::QGIFace(int index) : - projIndex(index), m_hideSvgTiles(false), + projIndex(index), m_hatchRotation(0.0) { - m_segCount = 0; -// setFillMode(NoFill); isHatched(false); setFlag(QGraphicsItem::ItemClipsChildrenToShape, true); @@ -68,26 +70,27 @@ QGIFace::QGIFace(int index) : m_pen.setStyle(m_styleCurrent); setLineWeight(0.0); //0 = cosmetic - setPrettyNormal(); m_texture = QPixmap(); //empty texture m_svgHatchArea = new QGCustomRect(); m_svgHatchArea->setParentItem(this); - - m_svgCol = SVGCOLDEFAULT; - m_fillScale = 1.0; + m_imageSvgHatchArea = new QGCustomImage(); + m_imageSvgHatchArea->setParentItem(this); getParameters(); // set up style & colour defaults - m_colDefFill = App::Color(static_cast(Preferences::getPreferenceGroup("Colors")->GetUnsigned("FaceColor", 0xFFFFFF))) + m_colDefFill = App::Color(static_cast(Preferences::getPreferenceGroup("Colors")->GetUnsigned("FaceColor", COLWHITE))) .asValue(); - m_colDefFill.setAlpha(Preferences::getPreferenceGroup("Colors")->GetBool("ClearFace", false) ? 0 : 255); + m_colDefFill.setAlpha(Preferences::getPreferenceGroup("Colors")->GetBool("ClearFace", false) ? ALPHALOW : ALPHAHIGH); m_fillDef = Qt::SolidPattern; m_fillSelect = Qt::SolidPattern; - setFillMode(m_colDefFill.alpha() ? PlainFill : NoFill); + setFillMode(NoFill); + if (m_colDefFill.alpha() > 0) { + setFillMode(PlainFill); + } setFill(m_colDefFill, m_fillDef); m_sharedRender = new QSvgRenderer(); @@ -106,6 +109,9 @@ void QGIFace::draw() // Base::Console().Message("QGIF::draw - pen style: %d\n", m_pen.style()); setPath(m_outline); //Face boundary + m_svgHatchArea->hide(); + m_imageSvgHatchArea->hide(); + if (isHatched()) { if (m_mode == GeomHatchFill) { //GeomHatch does not appear in pdf if clipping is set to true @@ -118,29 +124,28 @@ void QGIFace::draw() lineSetToFillItems(ls); } } - m_svgHatchArea->hide(); } else if (m_mode == SvgFill) { m_brush.setTexture(QPixmap()); m_fillNormal = m_fillDef; m_fillStyleCurrent = m_fillNormal; + setFlag(QGraphicsItem::ItemClipsChildrenToShape,true); loadSvgHatch(m_fileSpec); - //SVG tiles need to be clipped - setFlag(QGraphicsItem::ItemClipsChildrenToShape, true); - buildSvgHatch(); - m_svgHatchArea->show(); + if (exporting()) { + buildPixHatch(); + m_imageSvgHatchArea->show(); + } else { + buildSvgHatch(); + m_svgHatchArea->show(); + } } else if (m_mode == BitmapFill) { m_fillStyleCurrent = Qt::TexturePattern; m_texture = textureFromBitmap(m_fileSpec); m_brush.setTexture(m_texture); - m_svgHatchArea->hide(); } else if (m_mode == PlainFill) { setFill(m_colNormalFill, m_fillNormal); - m_svgHatchArea->hide(); } - } else { - // face is not hatched - m_svgHatchArea->hide(); } + show(); } @@ -174,9 +179,9 @@ void QGIFace::setPrettySel() { } /// show or hide the edges of this face. Usually just for debugging -void QGIFace::setDrawEdges(bool b) { +void QGIFace::setDrawEdges(bool state) { // Base::Console().Message("QGIF::setDrawEdges(%d)\n", b); - if (b) { + if (state) { setStyle(Qt::DashLine); } else { setStyle(Qt::NoPen); //don't draw face lines, just fill @@ -192,12 +197,12 @@ void QGIFace::setHatchFile(std::string fileSpec) void QGIFace::loadSvgHatch(std::string fileSpec) { QString qfs(QString::fromUtf8(fileSpec.data(), fileSpec.size())); - QFile f(qfs); - if (!f.open(QFile::ReadOnly | QFile::Text)) { + QFile file(qfs); + if (!file.open(QFile::ReadOnly | QFile::Text)) { Base::Console().Error("QGIFace could not read %s\n", fileSpec.c_str()); return; } - m_svgXML = f.readAll(); + m_svgXML = file.readAll(); // search in the file for the "stroke" specification in order to find out what declaration style is used // this is necessary to apply a color set by the user to the SVG @@ -211,9 +216,9 @@ void QGIFace::loadSvgHatch(std::string fileSpec) } } -void QGIFace::setFillMode(QGIFace::fillMode m) +void QGIFace::setFillMode(QGIFace::fillMode mode) { - m_mode = m; + m_mode = mode; if ((m_mode == NoFill) || (m_mode == PlainFill)) { isHatched(false); @@ -265,13 +270,13 @@ QPen QGIFace::setGeomPen() double QGIFace::getXForm() { //try to keep the pattern the same when View scales - auto s = scene(); - if (s) { - auto vs = s->views(); //ptrs to views - if (!vs.empty()) { - auto v = vs.at(0); - auto i = v->transform().inverted(); - return i.m11(); + auto ourScene = scene(); + if (ourScene) { + auto viewsAll = ourScene->views(); //ptrs to views + if (!viewsAll.empty()) { + auto view = viewsAll.at(0); + auto iView = view->transform().inverted(); + return iView.m11(); } } return 1.0; @@ -280,15 +285,15 @@ double QGIFace::getXForm() /// remove the children that make up a PAT fill void QGIFace::clearFillItems() { - for (auto& f: m_fillItems) { - f->setParentItem(nullptr); - this->scene()->removeItem(f); - delete f; + for (auto& fill: m_fillItems) { + fill->setParentItem(nullptr); + this->scene()->removeItem(fill); + delete fill; } } /// debugging tool draws a mark at a position on this face -void QGIFace::makeMark(double x, double y) +void QGIFace::makeMark(double x, double y) // NOLINT readability-identifier-length { QGICMark* cmItem = new QGICMark(-1); cmItem->setParentItem(this); @@ -315,9 +320,9 @@ void QGIFace::buildSvgHatch() double overlayHeight = tilesHigh * hTile; m_svgHatchArea->setRect(0., 0., overlayWidth,-overlayHeight); m_svgHatchArea->centerAt(faceCenter); - QByteArray before, after; - before = QString::fromStdString(SVGCOLPREFIX + SVGCOLDEFAULT).toUtf8(); - after = QString::fromStdString(SVGCOLPREFIX + m_svgCol).toUtf8(); + + QByteArray before = QString::fromStdString(SVGCOLPREFIX + SVGCOLDEFAULT).toUtf8(); + QByteArray after = QString::fromStdString(SVGCOLPREFIX + m_svgCol).toUtf8(); QByteArray colorXML = m_svgXML.replace(before, after); if (!m_sharedRender->load(colorXML)) { Base::Console().Message("QGIF::buildSvgHatch - failed to load svg string\n"); @@ -352,56 +357,138 @@ void QGIFace::clearSvg() hideSvg(true); } -//this isn't used currently -QPixmap QGIFace::textureFromSvg(std::string fileSpec) +//! similar to svg hatch, but using pixmaps. we do this because QGraphicsSvgItems are not clipped +//! when we export the scene to svg, but pixmaps are clipped. +void QGIFace::buildPixHatch() { - QString qs(QString::fromStdString(fileSpec)); - QFileInfo ffi(qs); - if (!ffi.isReadable()) { - return QPixmap(); + double wTile = SVGSIZEW * m_fillScale; + double hTile = SVGSIZEH * m_fillScale; + double faceWidth = m_outline.boundingRect().width(); + double faceHeight = m_outline.boundingRect().height(); + double faceOverlaySize = Preferences::svgHatchFactor() * std::max(faceWidth, faceHeight); + QPointF faceCenter = m_outline.boundingRect().center(); + double tilesWide = ceil(faceOverlaySize / wTile); + double tilesHigh = ceil(faceOverlaySize / hTile); + + double overlayWidth = tilesWide * wTile; + double overlayHeight = tilesHigh * hTile; + + // handle color by brute force find & replace + QByteArray before = QString::fromStdString(SVGCOLPREFIX + SVGCOLDEFAULT).toUtf8(); + QByteArray after = QString::fromStdString(SVGCOLPREFIX + m_svgCol).toUtf8(); + QByteArray colorXML = m_svgXML.replace(before,after); + + + // TODO: there is a lot of switching back and forth between svg, QPixmap and QImage here that I + // don't really understand. + // render svg tile onto a QImage + if (!m_sharedRender->load(colorXML)) { + Base::Console().Message("QGIF::buildSvgHatch - failed to load svg string\n"); + return; } - QSvgRenderer renderer(qs); - QPixmap pixMap(renderer.defaultSize()); - pixMap.fill(Qt::white); //try Qt::transparent? - QPainter painter(&pixMap); - renderer.render(&painter); //svg texture -> bitmap - return pixMap.scaled(m_fillScale, m_fillScale); + + QImage svgImage(SVGSIZEW, SVGSIZEH, QImage::Format_ARGB32); + svgImage.fill(Qt::transparent); + QPainter painter(&svgImage); + if (svgImage.isNull()) { + Base::Console().Error("QGIF::buildPixHatch - svgImage is null\n"); + return; + } + + m_sharedRender->render(&painter); + + // convert the QImage into a QPixmap + QPixmap tilePixmap(SVGSIZEW, SVGSIZEH); + tilePixmap = QPixmap::fromImage(svgImage); + tilePixmap = tilePixmap.scaled(wTile, hTile); + if (tilePixmap.isNull()) { + Base::Console().Error("QGIF::buildPixHatch - tilePixmap is null\n"); + return; + } + + // layout a field of bitmap tiles big enough to cover this face onto a Qimage + QImage tileField(overlayWidth, overlayHeight, QImage::Format_ARGB32); + QPointF fieldCenter(overlayWidth / 2.0, overlayHeight / 2.0); + + tileField.fill(Qt::transparent); + QPainter painter2(&tileField); + QPainter::RenderHints hints = painter2.renderHints(); + hints = hints & QPainter::Antialiasing; + painter2.setRenderHints(hints); + QPainterPath clipper = path(); + QPointF offset = (fieldCenter - faceCenter); + clipper.translate(offset); + painter2.setClipPath(clipper); + + long int tileCount = 0; + for (int iw = 0; iw < int(tilesWide); iw++) { + for (int ih = 0; ih < int(tilesHigh); ih++) { + painter2.drawPixmap(QRectF(iw * wTile, ih * hTile, wTile, hTile), //target rect + tilePixmap, + QRectF(0, 0, wTile, hTile)); //source rect + tileCount++; + if (tileCount > m_maxTile) { + Base::Console().Warning("Pixmap tile count exceeded: %ld\n",tileCount); + break; + } + } + if (tileCount > m_maxTile) { + break; + } + } + + // turn the QImage field into a pixmap + QPixmap fieldPixmap(overlayWidth, overlayHeight); + fieldPixmap = QPixmap::fromImage(tileField); + + // TODO: figure out how to rotate the pixmap without it looking terrible - far worse than the unrotated pixmap. svg hatch exported to svg will not be shown rotated + // QTransform xFormPixmap; + // xFormPixmap.rotate(m_hatchRotation); + // xFormPixmap.translate(getHatchOffset().x, getHatchOffset().y); + // m_imageSvgHatchArea->load(fieldPixmap.transformed(xFormPixmap)); + + QPixmap nothing; + m_imageSvgHatchArea->setPixmap(nothing); + m_imageSvgHatchArea->load(fieldPixmap); + m_imageSvgHatchArea->centerAt(faceCenter); + } -void QGIFace::setHatchColor(App::Color c) + +void QGIFace::setHatchColor(App::Color color) { - m_svgCol = c.asHexString(); - m_geomColor = c.asValue(); + m_svgCol = color.asHexString(); + m_geomColor = color.asValue(); } -void QGIFace::setHatchScale(double s) +void QGIFace::setHatchScale(double scale) { - m_fillScale = s; + m_fillScale = scale; } /// turn svg tiles on or off. QtSvg does not handle clipping, /// so we must be able to turn the hatching on/off when exporting a face with an /// svg hatch. Otherwise the full tile pattern is shown in the export. /// NOTE: there appears to have been a change in Qt that it now clips svg items -void QGIFace::hideSvg(bool b) +void QGIFace::hideSvg(bool state) { - m_hideSvgTiles = b; + m_hideSvgTiles = state; } /// create a QPixmap from a bitmap file. The QPixmap will be used as a QBrush /// texture. -QPixmap QGIFace::textureFromBitmap(std::string fileSpec) +QPixmap QGIFace::textureFromBitmap(std::string fileSpec) const { QPixmap pix; QString qfs(QString::fromUtf8(fileSpec.data(), fileSpec.size())); - QFile f(qfs); - if (!f.open(QFile::ReadOnly)) { + QFile file(qfs); + if (!file.open(QFile::ReadOnly)) { Base::Console().Error("QGIFace could not read %s\n", fileSpec.c_str()); return pix; } - QByteArray bytes = f.readAll(); + QByteArray bytes = file.readAll(); pix.loadFromData(bytes); if (m_hatchRotation != 0.0) { QTransform rotator; @@ -411,14 +498,14 @@ QPixmap QGIFace::textureFromBitmap(std::string fileSpec) return pix; } -void QGIFace::setLineWeight(double w) { - m_geomWeight = w; +void QGIFace::setLineWeight(double weight) { + m_geomWeight = weight; } void QGIFace::getParameters() { - m_maxSeg = Preferences::getPreferenceGroup("PAT")->GetInt("MaxSeg", 10000l); - m_maxTile = Preferences::getPreferenceGroup("Decorations")->GetInt("MaxSVGTile", 10000l); + m_maxSeg = Preferences::getPreferenceGroup("PAT")->GetInt("MaxSeg", MAXSEGMENT); + m_maxTile = Preferences::getPreferenceGroup("Decorations")->GetInt("MaxSVGTile", MAXTILES); } QRectF QGIFace::boundingRect() const @@ -430,3 +517,13 @@ QPainterPath QGIFace::shape() const { return path(); } + +bool QGIFace::exporting() const +{ + auto tdScene = dynamic_cast(scene()); + if (!tdScene) { + return false; + } + return tdScene->getExportingSvg(); +} + diff --git a/src/Mod/TechDraw/Gui/QGIFace.h b/src/Mod/TechDraw/Gui/QGIFace.h index 18323799b5..b0c8c3d317 100644 --- a/src/Mod/TechDraw/Gui/QGIFace.h +++ b/src/Mod/TechDraw/Gui/QGIFace.h @@ -43,9 +43,14 @@ class QGCustomSvg; class QGCustomRect; class QGCustomImage; - const double SVGSIZEW = 64.0; //width and height of standard FC SVG pattern - const double SVGSIZEH = 64.0; - const std::string SVGCOLDEFAULT = "#000000"; + constexpr int SVGSIZEW{64}; //width and height of standard FC SVG pattern + constexpr int SVGSIZEH{64}; + constexpr uint32_t COLWHITE{0xfffff}; // white + constexpr int ALPHALOW{0}; + constexpr int ALPHAHIGH{255}; + constexpr long int MAXSEGMENT{10000L}; + constexpr long int MAXTILES{10000L}; + const std::string SVGCOLDEFAULT = "#000000"; // black class QGIFace : public QGIPrimPath { @@ -68,35 +73,37 @@ public: PlainFill }; std::string SVGCOLPREFIX = ""; // will be determined on runtime - int getProjIndex() const { return projIndex; } void draw(); void setPrettyNormal() override; void setPrettyPre() override; void setPrettySel() override; - void setDrawEdges(bool b); + void setDrawEdges(bool state); virtual void setOutline(const QPainterPath& path); //shared fill parms - void isHatched(bool s) {m_isHatched = s; } + void isHatched(bool state) {m_isHatched = state; } bool isHatched() {return m_isHatched;} - void setFillMode(fillMode m); + void setFillMode(fillMode mode); //general hatch parms & methods - void setHatchColor(App::Color c); - void setHatchScale(double s); + void setHatchColor(App::Color color); + void setHatchScale(double scale); //svg fill parms & methods void setHatchFile(std::string fileSpec); void loadSvgHatch(std::string fileSpec); void buildSvgHatch(); - void hideSvg(bool b); + void hideSvg(bool state); void clearSvg(); + //tiled pixmap fill from svg + void buildPixHatch(); + //PAT fill parms & methods - void setGeomHatchWeight(double w) { m_geomWeight = w; } - void setLineWeight(double w); + void setGeomHatchWeight(double weight) { m_geomWeight = weight; } + void setLineWeight(double weight); void clearLineSets(); void addLineSet(TechDraw::LineSet& ls); @@ -104,13 +111,10 @@ public: void lineSetToFillItems(TechDraw::LineSet& ls); QGraphicsPathItem* geomToLine(TechDraw::BaseGeomPtr base, TechDraw::LineSet& ls); -// QGraphicsPathItem* geomToOffsetLine(TechDraw::BaseGeomPtr base, double offset, const TechDraw::LineSet& ls); QGraphicsPathItem* geomToStubbyLine(TechDraw::BaseGeomPtr base, double offset, TechDraw::LineSet& ls); QGraphicsPathItem* lineFromPoints(Base::Vector3d start, Base::Vector3d end, TechDraw::DashSpec ds); - //bitmap texture fill parms method - QPixmap textureFromBitmap(std::string fileSpec); - QPixmap textureFromSvg(std::string fillSpec); + QPixmap textureFromBitmap(std::string fileSpec) const; //Qt uses clockwise degrees void setHatchRotation(double degrees) { m_hatchRotation = -degrees; } @@ -120,39 +124,46 @@ public: Base::Vector3d getHatchOffset() { return m_hatchOffset; } protected: - void makeMark(double x, double y); + void makeMark(double x, double y); // NOLINT readability-identifier-length double getXForm(); void getParameters(); std::vector offsetDash(const std::vector dv, const double offset); QPainterPath dashedPPath(const std::vector dv, const Base::Vector3d start, const Base::Vector3d end); double dashRemain(const std::vector dv, const double offset); - double calcOffset(TechDraw::BaseGeomPtr g, TechDraw::LineSet ls); - int projIndex; //index of face in Projection. -1 for SectionFace. - QGCustomRect* m_svgHatchArea; + double calcOffset(TechDraw::BaseGeomPtr geom, TechDraw::LineSet ls); - QByteArray m_svgXML; - std::string m_svgCol; - std::string m_fileSpec; //for svg & bitmaps - - double m_fillScale; - bool m_isHatched; - QGIFace::fillMode m_mode; QPen setGeomPen(); - std::vector decodeDashSpec(TechDraw::DashSpec d); - std::vector m_fillItems; - std::vector m_lineSets; - std::vector m_dashSpecs; - long int m_segCount; - long int m_maxSeg; - long int m_maxTile; + std::vector decodeDashSpec(TechDraw::DashSpec dash); - bool m_hideSvgTiles; bool multiselectEligible() override { return true; } + bool exporting() const; + + private: + std::vector m_fillItems; + std::vector m_lineSets; + std::vector m_dashSpecs; + long int m_segCount{0}; + long int m_maxSeg{0}; + long int m_maxTile{0}; + + bool m_hideSvgTiles{false}; + int projIndex; //index of face in Projection. -1 for SectionFace. + + QGCustomRect* m_svgHatchArea; + QGCustomImage* m_imageSvgHatchArea; + + QByteArray m_svgXML; + std::string m_svgCol{SVGCOLDEFAULT}; + std::string m_fileSpec; //for svg & bitmaps + + double m_fillScale{1.0}; + bool m_isHatched{false}; + QGIFace::fillMode m_mode; QPixmap m_texture; // QPainterPath m_outline; // @@ -160,11 +171,11 @@ private: QPainterPath m_geomhatch; //crosshatch fill lines QColor m_geomColor; //color for crosshatch lines - double m_geomWeight; //lineweight for crosshatch lines + double m_geomWeight{0.5}; //lineweight for crosshatch lines QColor m_defFaceColor; - double m_hatchRotation; + double m_hatchRotation{0.0}; Base::Vector3d m_hatchOffset; QSvgRenderer *m_sharedRender; diff --git a/src/Mod/TechDraw/Gui/QGSPage.cpp b/src/Mod/TechDraw/Gui/QGSPage.cpp index c7c32bc34d..176b9ca67b 100644 --- a/src/Mod/TechDraw/Gui/QGSPage.cpp +++ b/src/Mod/TechDraw/Gui/QGSPage.cpp @@ -930,8 +930,10 @@ void QGSPage::redraw1View(TechDraw::DrawView* dView) // RichTextAnno needs to know when it is rendering an Svg as the font size // is handled differently in Svg compared to the screen or Pdf. +// Also true of QGraphicsSvgItems. void QGSPage::setExportingSvg(bool enable) { + m_exportingSvg = enable; QList sceneItems = items(); for (auto& qgi : sceneItems) { QGIRichAnno* qgiRTA = dynamic_cast(qgi); diff --git a/src/Mod/TechDraw/Gui/QGSPage.h b/src/Mod/TechDraw/Gui/QGSPage.h index f15280ba2a..7d46879bb1 100644 --- a/src/Mod/TechDraw/Gui/QGSPage.h +++ b/src/Mod/TechDraw/Gui/QGSPage.h @@ -129,6 +129,7 @@ public: TechDraw::DrawPage* getDrawPage(); void setExportingSvg(bool enable); + bool getExportingSvg() { return m_exportingSvg; } virtual void refreshViews(); /// Renders the page to SVG with filename. @@ -145,6 +146,8 @@ protected: private: QGITemplate* pageTemplate; ViewProviderPage* m_vpPage; + + bool m_exportingSvg{false}; }; }// namespace TechDrawGui From 590aa2e7ea69211a7bf85f689b5f36cd61724c92 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Thu, 11 Apr 2024 11:36:30 -0500 Subject: [PATCH 07/24] GitHub: Fix YAML format for labeler@v5 --- .github/labels.yml | 66 +++++++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/.github/labels.yml b/.github/labels.yml index 8c771c9f3c..6854e4100f 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -3,67 +3,85 @@ # - any: ['src/**/*', '!src/docs/*'] Core: -- 'src/App/**/*' -- 'src/Base/**/*' -- 'src/Gui/**/*' +- changed-files: + - any-glob-to-any-file: ['src/App/**/*', 'src/Base/**/*', 'src/Gui/**/*'] :octocat:: -- '.github/**/*' +- changed-files: + - any-glob-to-any-file: ['.github/**/*'] Addon Manager: -- 'src/Mod/AddonManager/**/*' +- changed-files: + - any-glob-to-any-file: ['src/Mod/AddonManager/**/*'] Materials: -- 'src/Mod/Material/**/*' +- changed-files: + - any-glob-to-any-file: ['src/Mod/Material/**/*'] WB Arch: -- 'src/Mod/Arch/**/*' +- changed-files: + - any-glob-to-any-file: ['src/Mod/Arch/**/*'] WB Assembly: -- 'src/Mod/Assembly/**/*' +- changed-files: + - any-glob-to-any-file: ['src/Mod/Assembly/**/*'] WB Draft: -- 'src/Mod/Draft/**/*' +- changed-files: + - any-glob-to-any-file: ['src/Mod/Draft/**/*'] WB FEM: -- 'src/Mod/Fem/**/*' +- changed-files: + - any-glob-to-any-file: ['src/Mod/Fem/**/*'] WB Mesh: -- 'src/Mod/Mesh/**/*' +- changed-files: + - any-glob-to-any-file: ['src/Mod/Mesh/**/*'] WB MeshPart: -- 'src/Mod/MeshPart/**/*' +- changed-files: + - any-glob-to-any-file: ['src/Mod/MeshPart/**/*'] WB OpenSCAD: -- 'src/Mod/OpenSCAD/**/*' +- changed-files: + - any-glob-to-any-file: ['src/Mod/OpenSCAD/**/*'] WB Part: -- 'src/Mod/Part/**/*' +- changed-files: + - any-glob-to-any-file: ['src/Mod/Part/**/*'] WB Part Design: -- 'src/Mod/PartDesign/**/*' +- changed-files: + - any-glob-to-any-file: ['src/Mod/PartDesign/**/*'] WB CAM: -- 'src/Mod/CAM/**/*' +- changed-files: + - any-glob-to-any-file: ['src/Mod/CAM/**/*'] WB Points: -- 'src/Mod/Points/**/*' +- changed-files: + - any-glob-to-any-file: ['src/Mod/Points/**/*'] WB ReverseEngineering: -- 'src/Mod/ReverseEngineering/**/*' +- changed-files: + - any-glob-to-any-file: ['src/Mod/ReverseEngineering/**/*'] WB Sketcher: -- 'src/Mod/Sketcher/**/*' +- changed-files: + - any-glob-to-any-file: ['src/Mod/Sketcher/**/*'] WB Spreadsheet: -- 'src/Mod/Spreadsheet/**/*' +- changed-files: + - any-glob-to-any-file: ['src/Mod/Spreadsheet/**/*'] WB Surface: -- 'src/Mod/Surface/**/*/' +- changed-files: + - any-glob-to-any-file: ['src/Mod/Surface/**/*/'] WB TechDraw: -- 'src/Mod/TechDraw/**/*' +- changed-files: + - any-glob-to-any-file: ['src/Mod/TechDraw/**/*'] WB Test: -- 'src/Mod/Test/**/*' - +- changed-files: + - any-glob-to-any-file: ['src/Mod/Test/**/*'] From 40c39ad8973aaff8c82e6ca91744d882b4b855d0 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Wed, 10 Apr 2024 10:29:57 -0400 Subject: [PATCH 08/24] Toponaming/Part: Transfer in python ElementMapVersion --- src/App/DocumentObject.cpp | 18 ++++++++++++++++++ src/App/DocumentObject.h | 14 ++++++++++++++ src/App/DocumentObjectPy.xml | 7 +++++++ src/App/DocumentObjectPyImp.cpp | 16 ++++++++++++++++ src/App/GeoFeaturePy.xml | 6 ++++++ src/App/GeoFeaturePyImp.cpp | 5 +++++ 6 files changed, 66 insertions(+) diff --git a/src/App/DocumentObject.cpp b/src/App/DocumentObject.cpp index 5fa4e91c93..ad2bb4bacb 100644 --- a/src/App/DocumentObject.cpp +++ b/src/App/DocumentObject.cpp @@ -1304,6 +1304,24 @@ bool DocumentObject::adjustRelativeLinks( return touched; } +std::string DocumentObject::getElementMapVersion(const App::Property* _prop, bool restored) const +{ + auto prop = Base::freecad_dynamic_cast(_prop); + if (!prop) { + return std::string(); + } + return prop->getElementMapVersion(restored); +} + +bool DocumentObject::checkElementMapVersion(const App::Property* _prop, const char* ver) const +{ + auto prop = Base::freecad_dynamic_cast(_prop); + if (!prop) { + return false; + } + return prop->checkElementMapVersion(ver); +} + const std::string &DocumentObject::hiddenMarker() { static std::string marker("!hide"); return marker; diff --git a/src/App/DocumentObject.h b/src/App/DocumentObject.h index 4cc24107d3..7052f92d7e 100644 --- a/src/App/DocumentObject.h +++ b/src/App/DocumentObject.h @@ -300,6 +300,20 @@ public: bool testIfLinkDAGCompatible(App::PropertyLinkSubList &linksTo) const; bool testIfLinkDAGCompatible(App::PropertyLinkSub &linkTo) const; + /** Return the element map version of the geometry data stored in the given property + * + * @param prop: the geometry property to query for element map version + * @param restored: whether to query for the restored element map version. + * In case of version upgrade, the restored version may + * be different from the current version. + * + * @return Return the element map version string. + */ + virtual std::string getElementMapVersion(const App::Property *prop, bool restored=false) const; + + /// Return true to signal re-generation of geometry element names + virtual bool checkElementMapVersion(const App::Property *prop, const char *ver) const; + public: /** mustExecute * We call this method to check if the object was modified to diff --git a/src/App/DocumentObjectPy.xml b/src/App/DocumentObjectPy.xml index 8792376ad9..7424fc9a58 100644 --- a/src/App/DocumentObjectPy.xml +++ b/src/App/DocumentObjectPy.xml @@ -225,6 +225,13 @@ Return tuple(obj,newElementName,oldElementName) adjustRelativeLinks(parent,recursive=True) -- auto correct potential cyclic dependencies + + + + getElementMapVersion(property_name): return element map version of a given geometry property + + + A list of all objects this object links to. diff --git a/src/App/DocumentObjectPyImp.cpp b/src/App/DocumentObjectPyImp.cpp index 4b3282a946..07344d2e93 100644 --- a/src/App/DocumentObjectPyImp.cpp +++ b/src/App/DocumentObjectPyImp.cpp @@ -745,6 +745,22 @@ PyObject* DocumentObjectPy::getPathsByOutList(PyObject *args) } } +PyObject* DocumentObjectPy::getElementMapVersion(PyObject* args) +{ + const char* name; + PyObject* restored = Py_False; + if (!PyArg_ParseTuple(args, "s|O", &name, &restored)) { + return NULL; + } + + Property* prop = getDocumentObjectPtr()->getPropertyByName(name); + if (!prop) { + throw Py::ValueError("property not found"); + } + return Py::new_reference_to( + Py::String(getDocumentObjectPtr()->getElementMapVersion(prop, Base::asBoolean(restored)))); +} + PyObject *DocumentObjectPy::getCustomAttributes(const char* ) const { return nullptr; diff --git a/src/App/GeoFeaturePy.xml b/src/App/GeoFeaturePy.xml index 83726bf70a..aeaefe7a4e 100644 --- a/src/App/GeoFeaturePy.xml +++ b/src/App/GeoFeaturePy.xml @@ -58,6 +58,12 @@ If an object has no such property then None is returned. Unlike to getPropertyNameOfGeometry this function returns the geometry, not its name. + + + Element map version + + + diff --git a/src/App/GeoFeaturePyImp.cpp b/src/App/GeoFeaturePyImp.cpp index ec2af688df..26a08a9f2d 100644 --- a/src/App/GeoFeaturePyImp.cpp +++ b/src/App/GeoFeaturePyImp.cpp @@ -93,3 +93,8 @@ int GeoFeaturePy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) { return 0; } + +Py::String GeoFeaturePy::getElementMapVersion() const { + return Py::String(getGeoFeaturePtr()->getElementMapVersion( + getGeoFeaturePtr()->getPropertyOfGeometry())); +} From b471435bc5795be5254be34c2d873686b7651dbf Mon Sep 17 00:00:00 2001 From: bgbsww Date: Wed, 10 Apr 2024 10:30:31 -0400 Subject: [PATCH 09/24] Toponaming/Part: Add Pad test, cleanup --- .../TestTopologicalNamingProblem.py | 75 +++++++++++++------ 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py b/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py index 22ed6d4f98..593e12fd5c 100644 --- a/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py +++ b/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py @@ -110,11 +110,38 @@ class TestTopologicalNamingProblem(unittest.TestCase): else: print("TOPOLOGICAL NAMING PROBLEM IS PRESENT.") + def testPartDesignElementMapPad(self): + """ Test that padding a sketch results in a correct element map. Note that comprehensive testing + of the geometric functionality of the Pad is in TestPad.py """ + # Arrange + body = self.Doc.addObject('PartDesign::Body', 'Body') + padSketch = self.Doc.addObject('Sketcher::SketchObject', 'SketchPad') + pad = self.Doc.addObject("PartDesign::Pad", "Pad") + body.addObject(padSketch) + body.addObject(pad) + TestSketcherApp.CreateRectangleSketch(padSketch, (0, 0), (1, 1)) + pad.Profile = padSketch + pad.Length = 1 + # Act + self.Doc.recompute() + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. + return + reverseMap = pad.Shape.ElementReverseMap + faces = [name for name in reverseMap.keys() if name.startswith("Face")] + edges = [name for name in reverseMap.keys() if name.startswith("Edge")] + vertexes = [name for name in reverseMap.keys() if name.startswith("Vertex")] + # Assert + self.assertEqual(pad.Shape.ElementMapSize,30) # 4 duplicated Vertexes in here + self.assertEqual(len(reverseMap),26) + self.assertEqual(len(faces),6) + self.assertEqual(len(edges),12) + self.assertEqual(len(vertexes),8) + def testPartDesignElementMapBox(self): # Arrange body = self.Doc.addObject('PartDesign::Body', 'Body') box = self.Doc.addObject('PartDesign::AdditiveBox', 'Box') - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act / Assert self.assertEqual(len(box.Shape.childShapes()), 0) @@ -131,7 +158,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): # Arrange body = self.Doc.addObject('PartDesign::Body', 'Body') cylinder = self.Doc.addObject('PartDesign::AdditiveCylinder', 'Cylinder') - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act / Assert self.assertEqual(len(cylinder.Shape.childShapes()), 0) @@ -148,7 +175,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): # Arrange body = self.Doc.addObject('PartDesign::Body', 'Body') sphere = self.Doc.addObject('PartDesign::AdditiveSphere', 'Sphere') - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act / Assert self.assertEqual(len(sphere.Shape.childShapes()), 0) @@ -165,7 +192,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): # Arrange body = self.Doc.addObject('PartDesign::Body', 'Body') cone = self.Doc.addObject('PartDesign::AdditiveCone', 'Cone') - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act / Assert self.assertEqual(len(cone.Shape.childShapes()), 0) @@ -182,7 +209,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): # Arrange body = self.Doc.addObject('PartDesign::Body', 'Body') ellipsoid = self.Doc.addObject('PartDesign::AdditiveEllipsoid', 'Ellipsoid') - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act / Assert self.assertEqual(len(ellipsoid.Shape.childShapes()), 0) @@ -199,7 +226,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): # Arrange body = self.Doc.addObject('PartDesign::Body', 'Body') torus = self.Doc.addObject('PartDesign::AdditiveTorus', 'Torus') - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act / Assert self.assertEqual(len(torus.Shape.childShapes()), 0) @@ -216,7 +243,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): # Arrange body = self.Doc.addObject('PartDesign::Body', 'Body') prism = self.Doc.addObject('PartDesign::AdditivePrism', 'Prism') - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act / Assert self.assertEqual(len(prism.Shape.childShapes()), 0) @@ -233,7 +260,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): # Arrange body = self.Doc.addObject('PartDesign::Body', 'Body') wedge = self.Doc.addObject('PartDesign::AdditiveWedge', 'Wedge') - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act / Assert self.assertEqual(len(wedge.Shape.childShapes()), 0) @@ -256,7 +283,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): box.Width = 20 box.Height = 20 body.addObject(box) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act subbox = self.Doc.addObject('PartDesign::SubtractiveBox', 'Box') @@ -275,7 +302,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): box.Width = 20 box.Height = 20 body.addObject(box) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act subcylinder = self.Doc.addObject('PartDesign::SubtractiveCylinder', 'Cylinder') @@ -294,7 +321,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): box.Width = 20 box.Height = 20 body.addObject(box) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act subsphere = self.Doc.addObject('PartDesign::SubtractiveSphere', 'Sphere') @@ -313,7 +340,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): box.Width = 20 box.Height = 20 body.addObject(box) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act subcone = self.Doc.addObject('PartDesign::SubtractiveCone', 'Cone') @@ -332,7 +359,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): box.Width = 20 box.Height = 20 body.addObject(box) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act subellipsoid = self.Doc.addObject('PartDesign::SubtractiveEllipsoid', 'Ellipsoid') @@ -351,7 +378,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): box.Width = 20 box.Height = 20 body.addObject(box) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act subtorus = self.Doc.addObject('PartDesign::SubtractiveTorus', 'Torus') @@ -370,7 +397,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): box.Width = 20 box.Height = 20 body.addObject(box) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act subprism = self.Doc.addObject('PartDesign::SubtractivePrism', 'Prism') @@ -389,7 +416,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): box.Width = 20 box.Height = 20 body.addObject(box) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act subwedge = self.Doc.addObject('PartDesign::SubtractiveWedge', 'Wedge') @@ -405,7 +432,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): body = self.Doc.addObject('PartDesign::Body', 'Body') sketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch') TestSketcherApp.CreateRectangleSketch(sketch, (0, 0), (1, 1)) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act pad = self.Doc.addObject('PartDesign::Pad', 'Pad') @@ -423,7 +450,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): body = self.Doc.addObject('PartDesign::Body', 'Body') sketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch') TestSketcherApp.CreateRectangleSketch(sketch, (0, 0), (1, 1)) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act revolution = self.Doc.addObject('PartDesign::Revolution', 'Revolution') @@ -444,7 +471,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): sketch2 = self.Doc.addObject('Sketcher::SketchObject', 'Sketch') TestSketcherApp.CreateRectangleSketch(sketch2, (0, 0), (2, 2)) sketch2.Placement.move(App.Vector(0, 0, 3)) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act loft = self.Doc.addObject('PartDesign::AdditiveLoft', 'Loft') @@ -465,7 +492,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): TestSketcherApp.CreateRectangleSketch(sketch, (0, 0), (1, 1)) sketch2 = self.Doc.addObject('Sketcher::SketchObject', 'Sketch') TestSketcherApp.CreateRectangleSketch(sketch2, (0, 0), (2, 2)) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act pipe = self.Doc.addObject('PartDesign::AdditivePipe', 'Pipe') @@ -484,7 +511,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): body = self.Doc.addObject('PartDesign::Body', 'Body') sketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch') TestSketcherApp.CreateRectangleSketch(sketch, (0, 0), (1, 1)) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act helix = self.Doc.addObject('PartDesign::AdditiveHelix', 'Helix') @@ -519,6 +546,8 @@ class TestTopologicalNamingProblem(unittest.TestCase): groove.Reversed = 0 groove.Base = App.Vector(0, 0, 0) self.Doc.recompute() + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. + return # Assert # print(groove.Shape.childShapes()[0].ElementMap) # TODO: Complete me as part of the subtractive features @@ -543,7 +572,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): pad.Profile = sketch body.addObject(pad) self.Doc.recompute() - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Assert self.assertEqual(sketch.Shape.ElementMapSize, 12) @@ -562,7 +591,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): pad = self.Doc.addObject('PartDesign::Pad', 'Pad') pad.Profile = plane self.Doc.recompute() - if not hasattr(pad,"ElementMapVersion"): # Skip without element maps. + if pad.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Assert self.assertEqual(plane.Shape.ElementMapSize, 0) From 11b4d218bdd93b97c1f2cd355496107f259074fa Mon Sep 17 00:00:00 2001 From: bgbsww Date: Wed, 10 Apr 2024 18:28:27 -0400 Subject: [PATCH 10/24] Toponaming/Part: Enhanced testing for FeaturePrimitives --- .../TestTopologicalNamingProblem.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py b/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py index 593e12fd5c..56ec3606e8 100644 --- a/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py +++ b/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py @@ -168,8 +168,16 @@ class TestTopologicalNamingProblem(unittest.TestCase): body.addObject(cylinder) self.assertEqual(len(body.Shape.childShapes()), 0) self.Doc.recompute() + reverseMap = body.Shape.childShapes()[0].ElementReverseMap + faces = [name for name in reverseMap.keys() if name.startswith("Face")] + edges = [name for name in reverseMap.keys() if name.startswith("Edge")] + vertexes = [name for name in reverseMap.keys() if name.startswith("Vertex")] self.assertEqual(len(body.Shape.childShapes()), 1) self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 8) + self.assertEqual(len(reverseMap),8) + self.assertEqual(len(faces),3) + self.assertEqual(len(edges),3) + self.assertEqual(len(vertexes),2) def testPartDesignElementMapSphere(self): # Arrange @@ -185,8 +193,16 @@ class TestTopologicalNamingProblem(unittest.TestCase): body.addObject(sphere) self.assertEqual(len(body.Shape.childShapes()), 0) self.Doc.recompute() + reverseMap = body.Shape.childShapes()[0].ElementReverseMap + faces = [name for name in reverseMap.keys() if name.startswith("Face")] + edges = [name for name in reverseMap.keys() if name.startswith("Edge")] + vertexes = [name for name in reverseMap.keys() if name.startswith("Vertex")] self.assertEqual(len(body.Shape.childShapes()), 1) self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 6) + self.assertEqual(len(reverseMap),6) + self.assertEqual(len(faces),1) + self.assertEqual(len(edges),3) + self.assertEqual(len(vertexes),2) def testPartDesignElementMapCone(self): # Arrange @@ -202,8 +218,16 @@ class TestTopologicalNamingProblem(unittest.TestCase): body.addObject(cone) self.assertEqual(len(body.Shape.childShapes()), 0) self.Doc.recompute() + reverseMap = body.Shape.childShapes()[0].ElementReverseMap + faces = [name for name in reverseMap.keys() if name.startswith("Face")] + edges = [name for name in reverseMap.keys() if name.startswith("Edge")] + vertexes = [name for name in reverseMap.keys() if name.startswith("Vertex")] self.assertEqual(len(body.Shape.childShapes()), 1) self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 8) + self.assertEqual(len(reverseMap),8) + self.assertEqual(len(faces),3) + self.assertEqual(len(edges),3) + self.assertEqual(len(vertexes),2) def testPartDesignElementMapEllipsoid(self): # Arrange @@ -219,8 +243,16 @@ class TestTopologicalNamingProblem(unittest.TestCase): body.addObject(ellipsoid) self.assertEqual(len(body.Shape.childShapes()), 0) self.Doc.recompute() + reverseMap = body.Shape.childShapes()[0].ElementReverseMap + faces = [name for name in reverseMap.keys() if name.startswith("Face")] + edges = [name for name in reverseMap.keys() if name.startswith("Edge")] + vertexes = [name for name in reverseMap.keys() if name.startswith("Vertex")] self.assertEqual(len(body.Shape.childShapes()), 1) self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 6) + self.assertEqual(len(reverseMap),6) + self.assertEqual(len(faces),1) + self.assertEqual(len(edges),3) + self.assertEqual(len(vertexes),2) def testPartDesignElementMapTorus(self): # Arrange @@ -236,8 +268,16 @@ class TestTopologicalNamingProblem(unittest.TestCase): body.addObject(torus) self.assertEqual(len(body.Shape.childShapes()), 0) self.Doc.recompute() + reverseMap = body.Shape.childShapes()[0].ElementReverseMap + faces = [name for name in reverseMap.keys() if name.startswith("Face")] + edges = [name for name in reverseMap.keys() if name.startswith("Edge")] + vertexes = [name for name in reverseMap.keys() if name.startswith("Vertex")] self.assertEqual(len(body.Shape.childShapes()), 1) self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 4) + self.assertEqual(len(reverseMap),4) + self.assertEqual(len(faces),1) + self.assertEqual(len(edges),2) + self.assertEqual(len(vertexes),1) def testPartDesignElementMapPrism(self): # Arrange @@ -253,8 +293,16 @@ class TestTopologicalNamingProblem(unittest.TestCase): body.addObject(prism) self.assertEqual(len(body.Shape.childShapes()), 0) self.Doc.recompute() + reverseMap = body.Shape.childShapes()[0].ElementReverseMap + faces = [name for name in reverseMap.keys() if name.startswith("Face")] + edges = [name for name in reverseMap.keys() if name.startswith("Edge")] + vertexes = [name for name in reverseMap.keys() if name.startswith("Vertex")] self.assertEqual(len(body.Shape.childShapes()), 1) self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 38) + self.assertEqual(len(reverseMap),38) + self.assertEqual(len(faces),8) + self.assertEqual(len(edges),18) + self.assertEqual(len(vertexes),12) def testPartDesignElementMapWedge(self): # Arrange @@ -270,8 +318,16 @@ class TestTopologicalNamingProblem(unittest.TestCase): body.addObject(wedge) self.assertEqual(len(body.Shape.childShapes()), 0) self.Doc.recompute() + reverseMap = body.Shape.childShapes()[0].ElementReverseMap + faces = [name for name in reverseMap.keys() if name.startswith("Face")] + edges = [name for name in reverseMap.keys() if name.startswith("Edge")] + vertexes = [name for name in reverseMap.keys() if name.startswith("Vertex")] self.assertEqual(len(body.Shape.childShapes()), 1) self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 26) + self.assertEqual(len(reverseMap),26) + self.assertEqual(len(faces),6) + self.assertEqual(len(edges),12) + self.assertEqual(len(vertexes),8) # body.BaseFeature = box From 74556bced867fdc8a8ac2b6e93b2b602d87e84b2 Mon Sep 17 00:00:00 2001 From: wandererfan Date: Tue, 9 Apr 2024 08:30:45 -0400 Subject: [PATCH 11/24] [TD]handle odd projection cs in broken view --- src/Mod/TechDraw/App/DrawBrokenView.cpp | 384 +++++++++++++++++------- src/Mod/TechDraw/App/DrawBrokenView.h | 17 +- src/Mod/TechDraw/App/DrawUtil.cpp | 24 +- src/Mod/TechDraw/App/DrawUtil.h | 2 + src/Mod/TechDraw/Gui/QGIBreakLine.cpp | 2 +- src/Mod/TechDraw/Gui/QGIViewPart.cpp | 7 +- 6 files changed, 307 insertions(+), 129 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawBrokenView.cpp b/src/Mod/TechDraw/App/DrawBrokenView.cpp index c44b216f94..0e28dc3b87 100644 --- a/src/Mod/TechDraw/App/DrawBrokenView.cpp +++ b/src/Mod/TechDraw/App/DrawBrokenView.cpp @@ -224,6 +224,10 @@ TopoDS_Shape DrawBrokenView::apply1Break(const App::DocumentObject& breakObj, co builder.MakeCompound(result); builder.Add(result, cut0); builder.Add(result, cut1); + + // BRepTools::Write(cut0, "DBVcut0.brep"); //debug + // BRepTools::Write(cut1, "DBVcut1.brep"); + return result; } @@ -241,32 +245,27 @@ TopoDS_Shape DrawBrokenView::compressShape(const TopoDS_Shape& shapeToCompress) //! move the broken pieces in the input shape "right" to close up the removed areas. //! note: breaks and pieces should not intersect by this point //! a break: BbbbbbbB -//! a piece: PpppP no need to move -//! a piece: PppppP move right by removed(B) +//! a piece: PpppP move right by removed(B) +//! a piece: PppppP no need to move TopoDS_Shape DrawBrokenView::compressHorizontal(const TopoDS_Shape& shapeToCompress)const { // Base::Console().Message("DBV::compressHorizontal()\n"); auto pieces = getPieces(shapeToCompress); auto breaksAll = Breaks.getValues(); - // ?? not sure about using closestBasis here. - auto moveDirection = DU::closestBasis(DU::toVector3d(getProjectionCS().XDirection())); + auto moveDirection = DU::closestBasisOriented(DU::toVector3d(getProjectionCS().XDirection())); bool descend = false; auto sortedBreaks = makeSortedBreakList(breaksAll, moveDirection, descend); - auto limits = getPieceUpperLimits(pieces, moveDirection); + auto limits = getPieceLimits(pieces, moveDirection); // for each break, move all the pieces left of the break to the right by the removed amount // for the break for (auto& breakItem : sortedBreaks) { // check each break against all the pieces - Base::Vector3d netBreakDisplace = moveDirection * (removedLengthFromObj(*breakItem.breakObj) - Gap.getValue()); + Base::Vector3d netBreakDisplace = + moveDirection * (removedLengthFromObj(*breakItem.breakObj) - Gap.getValue()); size_t iPiece{0}; - for (auto& pieceHighLimit : limits) { + for (auto& pieceLimit : limits) { // check each piece against the current break - // We have a problem with low digits here. The cut operations and later - // bounding box creation may generate pieceHighLimits that are slightly - // off. We know that the pieces were cut by a break, so we use a fuzzy - // comparison. - if (pieceHighLimit < breakItem.lowLimit || - DU::fpCompare(pieceHighLimit, breakItem.lowLimit, Precision::Confusion()) ) { + if (moveThisPiece(pieceLimit, breakItem, moveDirection)) { // piece is to left of break, so needs to move right TopoDS_Shape temp = ShapeUtils::moveShape(pieces.at(iPiece), netBreakDisplace); pieces.at(iPiece) = temp; @@ -293,20 +292,21 @@ TopoDS_Shape DrawBrokenView::compressVertical(const TopoDS_Shape& shapeToCompre auto pieces = getPieces(shapeToCompress); auto breaksAll = Breaks.getValues(); // not sure about using closestBasis here. may prevent oblique breaks later. - auto moveDirection = DU::closestBasis(DU::toVector3d(getProjectionCS().YDirection())); + auto moveDirection = DU::closestBasisOriented(DU::toVector3d(getProjectionCS().YDirection())); + bool descend = false; auto sortedBreaks = makeSortedBreakList(breaksAll, moveDirection, descend); - auto limits = getPieceUpperLimits(pieces, moveDirection); - // for each break, move all the pieces above the break down by the removed amount + auto limits = getPieceLimits(pieces, moveDirection); + // for each break, move all the pieces below the break up by the removed amount // for the break for (auto& breakItem : sortedBreaks) { // check each break against all the pieces - Base::Vector3d netBreakDisplace = moveDirection * (removedLengthFromObj(*breakItem.breakObj) - Gap.getValue()); + Base::Vector3d netBreakDisplace = + moveDirection * (removedLengthFromObj(*breakItem.breakObj) - Gap.getValue()); size_t iPiece{0}; - for (auto& pieceHighLimit : limits) { + for (auto& pieceLimit : limits) { // check each piece against the current break using a fuzzy equality - if (pieceHighLimit < breakItem.lowLimit || - DU::fpCompare(pieceHighLimit, breakItem.lowLimit, Precision::Confusion()) ) { + if (moveThisPiece(pieceLimit, breakItem, moveDirection)) { // piece is below the break, move it up TopoDS_Shape temp = ShapeUtils::moveShape(pieces.at(iPiece), netBreakDisplace); pieces.at(iPiece) = temp; @@ -330,6 +330,10 @@ TopoDS_Shape DrawBrokenView::compressVertical(const TopoDS_Shape& shapeToCompre //! plane normal) and a point inside the half space (pointInSpace). TopoDS_Shape DrawBrokenView::makeHalfSpace(Base::Vector3d planePoint, Base::Vector3d planeNormal, Base::Vector3d pointInSpace) const { + // Base::Console().Message("DBV::makeHalfSpace - planePoint: %s normal: %s pointInSpace: %s\n", + // DU::formatVector(planePoint).c_str(), + // DU::formatVector(planeNormal).c_str(), + // DU::formatVector(pointInSpace).c_str()); gp_Pnt origin = DU::togp_Pnt(planePoint); gp_Dir axis = DU::togp_Dir(planeNormal); gp_Pln plane(origin, axis); @@ -361,13 +365,21 @@ std::pair DrawBrokenView::breakPointsFromObj(con //! perpendicular to the break lines. Base::Vector3d DrawBrokenView::directionFromObj(const App::DocumentObject& breakObj) const { + // Base::Console().Message("DBV::directionFromObj()\n"); std::pair ends = breakPointsFromObj(breakObj); Base::Vector3d direction = ends.second - ends.first; direction.Normalize(); + // does this need to be oriented? return DU::closestBasis(direction); } +//! extract the breakDirection from the break object in a form the gui will understand. +Base::Vector3d DrawBrokenView::guiDirectionFromObj(const App::DocumentObject& breakObj) const +{ + return projectPoint(directionFromObj(breakObj)); +} + //! calculate the length to be removed as specified by break object. double DrawBrokenView::removedLengthFromObj(const App::DocumentObject& breakObj) const { @@ -445,12 +457,28 @@ std::pair DrawBrokenView::breakPointsFromSketch( TopoDS_Edge last = sketchEdges.back(); if ((isVertical(first) && isVertical(last)) || (isHorizontal(first) && isHorizontal(last))) { + // get points on each edge that are in line with the center of the sketch + // along the break direction + Bnd_Box edgeBox; + edgeBox.SetGap(0.0); + BRepBndLib::AddOptimal(first, edgeBox); + BRepBndLib::AddOptimal(last, edgeBox); + double xMin = 0, xMax = 0, yMin = 0, yMax = 0, zMin = 0, zMax = 0; + edgeBox.Get(xMin, yMin, zMin, xMax, yMax, zMax); + Base::Vector3d center( (xMin + xMax) / 2.0, + (yMin + yMax) / 2.0, + (zMin + zMax) / 2.0 ); auto ends0 = SU::getEdgeEnds(first); - // trouble here if the break points are wildly out of line? - // std::pair makeCardinal(p0, p1) to force horiz or vert? - auto break0 = (ends0.first + ends0.second) / 2.0; + auto dir0 = ends0.second - ends0.first; + dir0.Normalize(); + // get a point on edge first on a perpendicular line through center + auto break0 = center.Perpendicular(ends0.first, dir0); + auto ends1 = SU::getEdgeEnds(last); - auto break1 = (ends1.first + ends1.second) / 2.0; + auto dir1 = ends1.second - ends1.first; + dir1.Normalize(); + auto break1 = center.Perpendicular(ends1.first, dir1); + return { break0, break1 }; } @@ -496,31 +524,29 @@ std::pair DrawBrokenView::breakBoundsFromObj(con //! broken view. used in making break lines. std::pair DrawBrokenView::breakBoundsFromSketch(const App::DocumentObject& breakObj) const { - // Base::Console().Message("DBV::breakBoundsFromSketch()\n"); + //Base::Console().Message("DBV::breakBoundsFromSketch()\n"); std::pair breakPoints = breakPointsFromObj(breakObj); - Base::Vector3d anchor = (breakPoints.first + breakPoints.second) / 2.0; + Base::Vector3d center = (breakPoints.first + breakPoints.second) / 2.0; Base::Vector3d breakDir = directionFromObj(breakObj); breakDir.Normalize(); - Base::Vector3d lineDir = makePerpendicular(breakDir); - lineDir.Normalize(); - - // is this right? or do we need to project the points first? Should be alright - // if the break points are not skewed? - double removed = (breakPoints.first - breakPoints.second).Length(); + Base::Vector3d perpDir = makePerpendicular(breakDir); + perpDir.Normalize(); // get the midpoint of the zigzags - Base::Vector3d ptOnLine0 = anchor + breakDir * removed / 2.0; - Base::Vector3d ptOnLine1 = anchor - breakDir * removed / 2.0; + Base::Vector3d ptOnLine0 = center + breakDir * removedLengthFromObj(breakObj) / 2.0; + Base::Vector3d ptOnLine1 = center - breakDir * removedLengthFromObj(breakObj) / 2.0; double lineLength = breaklineLength(breakObj); - Base::Vector3d corner0 = ptOnLine0 - lineDir * lineLength / 2.0; - Base::Vector3d corner1 = ptOnLine1 + lineDir * lineLength / 2.0; + Base::Vector3d corner0 = ptOnLine0 - perpDir * lineLength / 2.0; + Base::Vector3d corner1 = ptOnLine1 + perpDir * lineLength / 2.0; corner0 = mapPoint3dToView(corner0); corner1 = mapPoint3dToView(corner1); + // these are unscaled, unrotated points return{corner0, corner1}; } + //! extract the boundary of the break lines from an edge std::pair DrawBrokenView::breakBoundsFromEdge(const App::DocumentObject& breakObj) const { @@ -732,15 +758,15 @@ std::vector DrawBrokenView::edgesFromCompound(TopoDS_Shape compound } -//! find the upper limits of each piece's bounding box in direction (if we support oblique projection directions, then the +//! find the limits of each piece's bounding box in cardinal direction (if we support oblique projection directions, then the //! piece will have to be transformed to align with OXYZ cardinal axes as in DrawViewPart::getSizeAlongVector) -std::vector DrawBrokenView::getPieceUpperLimits(const std::vector& pieces, Base::Vector3d direction) +PieceLimitList DrawBrokenView::getPieceLimits(const std::vector& pieces, Base::Vector3d direction) { // Base::Console().Message("DBV::getPieceUpperLimits(%s)\n", DU::formatVector(direction).c_str()); Base::Vector3d stdX{1.0, 0.0, 0.0}; Base::Vector3d stdY{0.0, 1.0, 0.0}; Base::Vector3d stdZ{0.0, 0.0, 1.0}; - std::vector limits; + PieceLimitList limits; limits.reserve(pieces.size()); for (auto& item : pieces) { Bnd_Box pieceBox; @@ -748,13 +774,21 @@ std::vector DrawBrokenView::getPieceUpperLimits(const std::vector DrawBrokenView::getPieces(TopoDS_Shape brokenShape) //! sort the breaks that match direction by their minimum limit BreakList DrawBrokenView::makeSortedBreakList(const std::vector& breaks, Base::Vector3d direction, bool descend) const { + // Base::Console().Message("DBV::makeSortedBreakList(%d, %s)\n", breaks.size(), + // DU::formatVector(direction).c_str()); Base::Vector3d stdX{1.0, 0.0, 0.0}; Base::Vector3d stdY{0.0, 1.0, 0.0}; Base::Vector3d stdZ{0.0, 0.0, 1.0}; @@ -786,7 +822,7 @@ BreakList DrawBrokenView::makeSortedBreakList(const std::vector= breakItem.highLimit) { - // leave alone, this break doesn't affect us - continue; - } + if (isDirectionReversed(direction)) { + if (pointCoord <= breakItem.lowLimit) { + // h--------l -ve + // p + // point is right/above break + // leave alone, this break doesn't affect us + continue; + } - if (pointCoord < breakItem.lowLimit || - DU::fpCompare(pointCoord, breakItem.lowLimit, Precision::Confusion()) ) { - // move right/up by the removed area less the gap - shift += removedLengthFromObj(*breakItem.breakObj) - Gap.getValue(); - continue; - } + if (pointCoord > breakItem.highLimit || + DU::fpCompare(pointCoord, breakItem.highLimit, Precision::Confusion()) ) { + // h--------l -ve + // p + // point is left/below break, but we + shift += removedLengthFromObj(*breakItem.breakObj) - Gap.getValue(); + continue; + } - // break.start < value < break.end - point is in the break area - // we move our point by a fraction of the Gap length - double penetration = pointCoord - breakItem.lowLimit; - double removed = removedLengthFromObj(*breakItem.breakObj); - double factor = 1 - (penetration / removed); - double netRemoved = breakItem.highLimit - factor * Gap.getValue(); - shift += netRemoved - pointCoord; + // h--------l -ve + // p + // break.start < value < break.end - point is in the break area + // we move our point by a fraction of the Gap length + double penetration = fabs(pointCoord - breakItem.highLimit); // start(high) to point + double removed = removedLengthFromObj(*breakItem.breakObj); + double factor = 1 - (penetration / removed); + double netRemoved = breakItem.lowLimit - factor * Gap.getValue(); + shift += fabs(netRemoved - pointCoord); + + + } else { + if (pointCoord >= breakItem.highLimit) { + // l--------h +ve + // p + // leave alone, this break doesn't affect us + continue; + } + + if (pointCoord < breakItem.lowLimit || + DU::fpCompare(pointCoord, breakItem.lowLimit, Precision::Confusion()) ) { + // l--------h +ve + // p + // move right/up by the removed area less the gap + shift += removedLengthFromObj(*breakItem.breakObj) - Gap.getValue(); + continue; + } + + // l--------h +ve + // p + // break.start < value < break.end - point is in the break area + // we move our point by a fraction of the Gap length + double penetration = fabs(pointCoord - breakItem.lowLimit); + double removed = removedLengthFromObj(*breakItem.breakObj); + double factor = 1 - (penetration / removed); + double netRemoved = breakItem.highLimit - factor * Gap.getValue(); + shift += fabs(netRemoved - pointCoord); + } } return shift; @@ -963,30 +1044,67 @@ double DrawBrokenView::shiftAmountShrink(double pointCoord, const BreakList& sor //! returns the amount a compressed coordinate needs to be shifted to reverse the effect of breaking //! the source shapes -double DrawBrokenView::shiftAmountExpand(double pointCoord, const BreakList& sortedBreaks) const +double DrawBrokenView::shiftAmountExpand(double pointCoord, Base::Vector3d direction, const BreakList& sortedBreaks) const { - // Base::Console().Message("DBV::shiftAmountExpand(%.3f, %d)\n", pointCoord, sortedBreaks.size()); + // Base::Console().Message("DBV::shiftAmountExpand(%.3f, %s, %d)\n", pointCoord, + // DU::formatVector(direction).c_str(), sortedBreaks.size()); double shift{0}; for (auto& breakItem : sortedBreaks) { - if (pointCoord >= breakItem.highLimit) { - // leave alone, this break doesn't affect us - continue; - } + if (isDirectionReversed(direction)) { + if (pointCoord <= breakItem.lowLimit) { + // h--------l -ve + // p + // leave alone, this break doesn't affect us + continue; + } - if (pointCoord < breakItem.lowLimit || - DU::fpCompare(pointCoord, breakItem.lowLimit, Precision::Confusion()) ) { - // move by the whole removed area - shift += breakItem.netRemoved; - continue; - } + if (pointCoord > breakItem.highLimit || + DU::fpCompare(pointCoord, breakItem.highLimit, Precision::Confusion()) ) { + // h--------l -ve + // p - // break.start < value < break.end - point is in the break area - // we move our point by the break's net removed * the penetration factor - double gapPenetration = pointCoord - breakItem.lowLimit; - double removed = removedLengthFromObj(*breakItem.breakObj); - double factor = 1 - gapPenetration / Gap.getValue(); - double shiftAmount = factor * (removed - Gap.getValue()); - shift += shiftAmount; + // move by the whole removed area + shift += breakItem.netRemoved; + continue; + } + + // h--------l -ve + // p + // break.start < value < break.end - point is in the break area + // we move our point by the break's net removed * the penetration factor + double gapPenetration = fabs(pointCoord - breakItem.lowLimit); + double removed = removedLengthFromObj(*breakItem.breakObj); + double factor = gapPenetration / Gap.getValue(); + double shiftAmount = factor * (removed - Gap.getValue()); + shift += shiftAmount; + + } else { + if (pointCoord >= breakItem.highLimit) { + // l--------h +ve + // p + // leave alone, this break doesn't affect us + continue; + } + + if (pointCoord < breakItem.lowLimit || + DU::fpCompare(pointCoord, breakItem.lowLimit, Precision::Confusion()) ) { + // l--------h +ve + // p + // move by the whole removed area + shift += breakItem.netRemoved; + continue; + } + + // l--------h +ve + // p + // break.start < value < break.end - point is in the break area + // we move our point by the break's net removed * the penetration factor + double gapPenetration = pointCoord - breakItem.lowLimit; + double removed = removedLengthFromObj(*breakItem.breakObj); + double factor = 1 - gapPenetration / Gap.getValue(); + double shiftAmount = factor * (removed - Gap.getValue()); + shift += shiftAmount; + } } return shift; @@ -1014,6 +1132,46 @@ Base::Vector3d DrawBrokenView::makePerpendicular(Base::Vector3d inDir) const return DU::toVector3d(gRotated); } +//! true if this piece should be moved +bool DrawBrokenView::moveThisPiece(PieceLimitEntry piece, + BreakListEntry breakItem, + Base::Vector3d moveDirection) const +{ + if (isDirectionReversed(moveDirection)) { + // -ve direction + if (piece.lowLimit > breakItem.highLimit || + DU::fpCompare(piece.lowLimit, breakItem.highLimit, Precision::Confusion()) ) { + return true; + } + } else { + // +ve direction + if (piece.highLimit < breakItem.lowLimit || + DU::fpCompare(piece.highLimit, breakItem.lowLimit, Precision::Confusion()) ) { + return true; + } + } + return false; +} + +//! true if direction is the reversed of a cardinal direction. (1.0, 0.0, 0.0) +//! returns false, (-1.0, 0.0, 0.0) returns true; +bool DrawBrokenView::isDirectionReversed(Base::Vector3d direction) const +{ + Base::Vector3d stdX{1.0, 0.0, 0.0}; + Base::Vector3d stdY{0.0, 1.0, 0.0}; + Base::Vector3d stdZ{0.0, 0.0, 1.0}; + if (DU::fpCompare(std::fabs(direction.Dot(stdX)), 1.0, EWTOLERANCE)) { + return DU::fpCompare(direction.Dot(stdX), -1.0, EWTOLERANCE); + } + if (DU::fpCompare(std::fabs(direction.Dot(stdY)), 1.0, EWTOLERANCE)) { + return DU::fpCompare(direction.Dot(stdY), -1.0, EWTOLERANCE); + } + if (DU::fpCompare(std::fabs(direction.Dot(stdZ)), 1.0, EWTOLERANCE)) { + return DU::fpCompare(direction.Dot(stdZ), -1.0, EWTOLERANCE); + } + return false; +} + void DrawBrokenView::printBreakList(const std::string& text, const BreakList& inBreaks) const { Base::Console().Message("DBV - %s\n", text.c_str()); diff --git a/src/Mod/TechDraw/App/DrawBrokenView.h b/src/Mod/TechDraw/App/DrawBrokenView.h index bbd336793c..0371da8f4a 100644 --- a/src/Mod/TechDraw/App/DrawBrokenView.h +++ b/src/Mod/TechDraw/App/DrawBrokenView.h @@ -47,9 +47,14 @@ struct BreakListEntry { // TODO: can the gap size change during the lifetime of BreakListEntry? if // so, we need to save the gap size @ creation time? }; - using BreakList = std::vector; +struct PieceLimitEntry { + double lowLimit; + double highLimit; +}; +using PieceLimitList = std::vector; + class TechDrawExport DrawBrokenView: public TechDraw::DrawViewPart { PROPERTY_HEADER_WITH_OVERRIDE(TechDraw::DrawBrokenView); @@ -75,6 +80,7 @@ public: std::pair breakBoundsFromObj(const App::DocumentObject& breakObj) const; Base::Vector3d directionFromObj(const App::DocumentObject& breakObj) const; + Base::Vector3d guiDirectionFromObj(const App::DocumentObject& breakObj) const; static bool isBreakObject(const App::DocumentObject& breakObj); static bool isBreakObjectSketch(const App::DocumentObject& breakObj); @@ -114,7 +120,7 @@ private: TopoDS_Shape compressHorizontal(const TopoDS_Shape& inShape) const; TopoDS_Shape compressVertical(const TopoDS_Shape& inShape) const; - static std::vector getPieceUpperLimits(const std::vector& pieces, Base::Vector3d direction); + static PieceLimitList getPieceLimits(const std::vector& pieces, Base::Vector3d direction); BreakList makeSortedBreakList(const std::vector& breaks, Base::Vector3d direction, bool descend = false) const; BreakList makeSortedBreakListCompressed(const std::vector& breaks, Base::Vector3d moveDirection, bool descend = false) const; @@ -123,8 +129,8 @@ private: static bool breakLess(const BreakListEntry& entry0, const BreakListEntry& entry1); // double pointToLimit(const Base::Vector3d& inPoint, const Base::Vector3d& direction) const; - double shiftAmountShrink(double pointCoord, const BreakList& sortedBreaks) const; - double shiftAmountExpand(double pointCoord, const BreakList& sortedBreaks) const; + double shiftAmountShrink(double pointCoord, Base::Vector3d direction, const BreakList& sortedBreaks) const; + double shiftAmountExpand(double pointCoord, Base::Vector3d direction, const BreakList& sortedBreaks) const; void printBreakList(const std::string& text, const BreakList& inBreaks) const; @@ -132,6 +138,9 @@ private: scalePair(std::pair inPair) const; Base::Vector3d makePerpendicular(Base::Vector3d inDir) const; + bool moveThisPiece(PieceLimitEntry piece, BreakListEntry breakItem, Base::Vector3d moveDirection) const; + bool isDirectionReversed(Base::Vector3d direction) const; + Base::Vector3d m_unbrokenCenter; TopoDS_Shape m_compressedShape; diff --git a/src/Mod/TechDraw/App/DrawUtil.cpp b/src/Mod/TechDraw/App/DrawUtil.cpp index 79f1a4b0b7..5eb2562c04 100644 --- a/src/Mod/TechDraw/App/DrawUtil.cpp +++ b/src/Mod/TechDraw/App/DrawUtil.cpp @@ -861,18 +861,15 @@ double DrawUtil::getWidthInDirection(gp_Dir direction, TopoDS_Shape& shape) //! cardinal direction or the reverse of a cardinal direction. gp_Vec DrawUtil::maskDirection(gp_Vec inVec, gp_Dir directionToMask) { - if (directionToMask.XYZ().IsEqual(gp::OX().Direction().XYZ(), EWTOLERANCE) || - directionToMask.XYZ().IsEqual(gp::OX().Direction().Reversed().XYZ(), EWTOLERANCE)) { - return {0.0, inVec.Y(), inVec.Z()}; + if (fpCompare(std::fabs(directionToMask.Dot(gp::OX().Direction().XYZ())), 1.0, EWTOLERANCE)) { + return {0.0, inVec.Y(), inVec.Z()}; } - if (directionToMask.XYZ().IsEqual(gp::OY().Direction().XYZ(), EWTOLERANCE) || - directionToMask.XYZ().IsEqual(gp::OY().Direction().Reversed().XYZ(), EWTOLERANCE)) { + if (fpCompare(std::fabs(directionToMask.Dot(gp::OY().Direction().XYZ())), 1.0, EWTOLERANCE)) { return {inVec.X(), 0.0, inVec.Z()}; } - if (directionToMask.XYZ().IsEqual(gp::OZ().Direction().XYZ(), EWTOLERANCE) || - directionToMask.XYZ().IsEqual(gp::OZ().Direction().Reversed().XYZ(), EWTOLERANCE)) { + if (fpCompare(std::fabs(directionToMask.Dot(gp::OZ().Direction().XYZ())), 1.0, EWTOLERANCE)) { return {inVec.X(), inVec.Y(), 0.0}; } @@ -880,6 +877,19 @@ gp_Vec DrawUtil::maskDirection(gp_Vec inVec, gp_Dir directionToMask) return {}; } +Base::Vector3d DrawUtil::maskDirection(Base::Vector3d inVec, Base::Vector3d directionToMask) +{ + return toVector3d(maskDirection(togp_Vec(inVec), togp_Vec(directionToMask))); +} + +//! get the coordinate of inPoint for the cardinal unit direction. +double DrawUtil::coordinateForDirection(Base::Vector3d inPoint, Base::Vector3d cardinal) +{ + auto masked = maskDirection(inPoint, cardinal); + auto stripped = inPoint - masked; + return stripped.x + stripped.y + stripped.z; +} + //based on Function provided by Joe Dowsett, 2014 double DrawUtil::sensibleScale(double working_scale) { diff --git a/src/Mod/TechDraw/App/DrawUtil.h b/src/Mod/TechDraw/App/DrawUtil.h index 1f4580efce..1bc948b91b 100644 --- a/src/Mod/TechDraw/App/DrawUtil.h +++ b/src/Mod/TechDraw/App/DrawUtil.h @@ -154,6 +154,8 @@ public: static double getWidthInDirection(gp_Dir direction, TopoDS_Shape& shape); static gp_Vec maskDirection(gp_Vec inVec, gp_Dir directionToMask); + static Base::Vector3d maskDirection(Base::Vector3d inVec, Base::Vector3d directionToMask); + static double coordinateForDirection(Base::Vector3d inPoint, Base::Vector3d cardinal); static double getDefaultLineWeight(std::string s); //! is pt between end1 and end2? diff --git a/src/Mod/TechDraw/Gui/QGIBreakLine.cpp b/src/Mod/TechDraw/Gui/QGIBreakLine.cpp index 4dd9cde99d..1862c836e5 100644 --- a/src/Mod/TechDraw/Gui/QGIBreakLine.cpp +++ b/src/Mod/TechDraw/Gui/QGIBreakLine.cpp @@ -75,7 +75,7 @@ void QGIBreakLine::draw() Base::Vector3d horizontal{1.0, 0.0, 0.0}; prepareGeometryChange(); double offset = zigzagWidth / 2.0; - if (m_direction.IsEqual(horizontal, EWTOLERANCE)) { + if (DU::fpCompare(fabs(m_direction.Dot(horizontal)), 1.0, EWTOLERANCE)) { // m_direction connects the two cut points. The zigzags have // to be perpendicular to m_direction // 2x vertical zigzag diff --git a/src/Mod/TechDraw/Gui/QGIViewPart.cpp b/src/Mod/TechDraw/Gui/QGIViewPart.cpp index afb6d5c45b..ff3cf87664 100644 --- a/src/Mod/TechDraw/Gui/QGIViewPart.cpp +++ b/src/Mod/TechDraw/Gui/QGIViewPart.cpp @@ -1056,13 +1056,12 @@ void QGIViewPart::drawBreakLines() QGIBreakLine* breakLine = new QGIBreakLine(); addToGroup(breakLine); - Base::Vector3d direction = dbv->directionFromObj(*breakObj); - direction.Normalize(); + Base::Vector3d direction = dbv->guiDirectionFromObj(*breakObj); breakLine->setDirection(direction); - // the bounds describe two corners of the removed area + // the bounds describe two corners of the removed area in the view std::pair bounds = dbv->breakBoundsFromObj(*breakObj); // the bounds are in 3d form, so we need to invert & rez them - Base::Vector3d topLeft = Rez::guiX(DU::invertY(bounds.first)); + Base::Vector3d topLeft = Rez::guiX(DU::invertY(bounds.first)); Base::Vector3d bottomRight = Rez::guiX(DU::invertY(bounds.second)); breakLine->setBounds(topLeft, bottomRight); breakLine->setPos(0.0, 0.0); From f1c7d388dd53dcd0e3230fd2c3b772569a3223ab Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 12 Apr 2024 19:26:22 +0200 Subject: [PATCH 12/24] Tests: add Parameter tests --- tests/src/Base/CMakeLists.txt | 1 + tests/src/Base/Parameter.cpp | 260 ++++++++++++++++++++++++++++++++++ 2 files changed, 261 insertions(+) create mode 100644 tests/src/Base/Parameter.cpp diff --git a/tests/src/Base/CMakeLists.txt b/tests/src/Base/CMakeLists.txt index 307c6c3228..9fe58910bd 100644 --- a/tests/src/Base/CMakeLists.txt +++ b/tests/src/Base/CMakeLists.txt @@ -11,6 +11,7 @@ target_sources( ${CMAKE_CURRENT_SOURCE_DIR}/DualQuaternion.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Handle.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Matrix.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Parameter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Placement.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Quantity.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Reader.cpp diff --git a/tests/src/Base/Parameter.cpp b/tests/src/Base/Parameter.cpp new file mode 100644 index 0000000000..704ba93e07 --- /dev/null +++ b/tests/src/Base/Parameter.cpp @@ -0,0 +1,260 @@ +#include "gtest/gtest.h" +#include + +class ParameterTest: public ::testing::Test +{ +protected: + static void SetUpTestSuite() + { + ParameterManager::Init(); + } + + void SetUp() override + { + config = ParameterManager::Create(); + } + + void TearDown() override + {} + + Base::Reference getConfig() const + { + return config; + } + + Base::Reference getCreateConfig() + { + config->CreateDocument(); + return config; + } + +private: + Base::Reference config; +}; + +// NOLINTBEGIN(cppcoreguidelines-*,readability-*) +TEST_F(ParameterTest, TestValid) +{ + auto cfg = getConfig(); + EXPECT_EQ(cfg.isValid(), true); + EXPECT_EQ(cfg.isNull(), false); +} + +TEST_F(ParameterTest, TestCreate) +{ + auto cfg = getCreateConfig(); + cfg->CheckDocument(); + EXPECT_TRUE(cfg->IsEmpty()); +} + +TEST_F(ParameterTest, TestGroup) +{ + auto cfg = getCreateConfig(); + auto grp = cfg->GetGroup("TopLevelGroup"); + EXPECT_EQ(grp->Parent(), static_cast(cfg)); + EXPECT_EQ(grp->Manager(), static_cast(cfg)); + + EXPECT_FALSE(cfg->IsEmpty()); + EXPECT_TRUE(grp->IsEmpty()); + + EXPECT_TRUE(cfg->HasGroup("TopLevelGroup")); + EXPECT_FALSE(cfg->HasGroup("Group")); + + EXPECT_EQ(cfg->GetGroups().size(), 1); +} + +TEST_F(ParameterTest, TestGroupName) +{ + auto cfg = getCreateConfig(); + auto grp = cfg->GetGroup("TopLevelGroup"); + EXPECT_STREQ(grp->GetGroupName(), "TopLevelGroup"); +} + +TEST_F(ParameterTest, TestPath) +{ + auto cfg = getCreateConfig(); + auto grp1 = cfg->GetGroup("TopLevelGroup"); + auto sub1 = grp1->GetGroup("Sub1"); + auto sub2 = sub1->GetGroup("Sub2"); + EXPECT_EQ(sub2->GetPath(), "TopLevelGroup/Sub1/Sub2"); +} + +TEST_F(ParameterTest, TestBool) +{ + auto cfg = getCreateConfig(); + auto grp = cfg->GetGroup("TopLevelGroup"); + grp->SetBool("Parameter1", false); + grp->SetBool("Parameter2", true); + EXPECT_EQ(grp->GetBool("Parameter1", true), false); + EXPECT_EQ(grp->GetBool("Parameter3", true), true); + EXPECT_EQ(grp->GetBool("Parameter3", false), false); + + EXPECT_TRUE(grp->GetBools("Test").empty()); + EXPECT_EQ(grp->GetBools().size(), 2); + EXPECT_EQ(grp->GetBools().at(0), false); + EXPECT_EQ(grp->GetBools().at(1), true); + EXPECT_EQ(grp->GetBoolMap().size(), 2); + + grp->RemoveBool("Parameter1"); + EXPECT_EQ(grp->GetBools().size(), 1); +} + +TEST_F(ParameterTest, TestInt) +{ + auto cfg = getCreateConfig(); + auto grp = cfg->GetGroup("TopLevelGroup"); + grp->SetInt("Parameter1", -15); + grp->SetInt("Parameter2", 25); + EXPECT_EQ(grp->GetInt("Parameter1", 2), -15); + EXPECT_EQ(grp->GetInt("Parameter3", 2), 2); + EXPECT_EQ(grp->GetInt("Parameter3", 4), 4); + + EXPECT_TRUE(grp->GetInts("Test").empty()); + EXPECT_EQ(grp->GetInts().size(), 2); + EXPECT_EQ(grp->GetInts().at(0), -15); + EXPECT_EQ(grp->GetInts().at(1), 25); + EXPECT_EQ(grp->GetIntMap().size(), 2); + + grp->RemoveInt("Parameter1"); + EXPECT_EQ(grp->GetInts().size(), 1); +} + +TEST_F(ParameterTest, TestUnsigned) +{ + auto cfg = getCreateConfig(); + auto grp = cfg->GetGroup("TopLevelGroup"); + grp->SetUnsigned("Parameter1", 15); + grp->SetUnsigned("Parameter2", 25); + EXPECT_EQ(grp->GetUnsigned("Parameter1", 2), 15); + EXPECT_EQ(grp->GetUnsigned("Parameter3", 2), 2); + EXPECT_EQ(grp->GetUnsigned("Parameter3", 4), 4); + + EXPECT_TRUE(grp->GetUnsigneds("Test").empty()); + EXPECT_EQ(grp->GetUnsigneds().size(), 2); + EXPECT_EQ(grp->GetUnsigneds().at(0), 15); + EXPECT_EQ(grp->GetUnsigneds().at(1), 25); + EXPECT_EQ(grp->GetUnsignedMap().size(), 2); + + grp->RemoveUnsigned("Parameter1"); + EXPECT_EQ(grp->GetUnsigneds().size(), 1); +} + +TEST_F(ParameterTest, TestFloat) +{ + auto cfg = getCreateConfig(); + auto grp = cfg->GetGroup("TopLevelGroup"); + grp->SetFloat("Parameter1", 1.5); + grp->SetFloat("Parameter2", 2.5); + EXPECT_EQ(grp->GetFloat("Parameter1", 2.0), 1.5); + EXPECT_EQ(grp->GetFloat("Parameter3", 2.0), 2.0); + EXPECT_EQ(grp->GetFloat("Parameter3", 4.0), 4.0); + + EXPECT_TRUE(grp->GetFloats("Test").empty()); + EXPECT_EQ(grp->GetFloats().size(), 2); + EXPECT_EQ(grp->GetFloats().at(0), 1.5); + EXPECT_EQ(grp->GetFloats().at(1), 2.5); + EXPECT_EQ(grp->GetFloatMap().size(), 2); + + grp->RemoveFloat("Parameter1"); + EXPECT_EQ(grp->GetFloats().size(), 1); +} + +TEST_F(ParameterTest, TestString) +{ + auto cfg = getCreateConfig(); + auto grp = cfg->GetGroup("TopLevelGroup"); + grp->SetASCII("Parameter1", "Value1"); + grp->SetASCII("Parameter2", "Value2"); + EXPECT_EQ(grp->GetASCII("Parameter1", "Value3"), "Value1"); + EXPECT_EQ(grp->GetASCII("Parameter3", "Value3"), "Value3"); + EXPECT_EQ(grp->GetASCII("Parameter3", "Value4"), "Value4"); + + EXPECT_TRUE(grp->GetASCIIs("Test").empty()); + EXPECT_EQ(grp->GetASCIIs().size(), 2); + EXPECT_EQ(grp->GetASCIIs().at(0), "Value1"); + EXPECT_EQ(grp->GetASCIIs().at(1), "Value2"); + EXPECT_EQ(grp->GetASCIIMap().size(), 2); + + grp->RemoveASCII("Parameter1"); + EXPECT_EQ(grp->GetASCIIs().size(), 1); +} + +TEST_F(ParameterTest, TestCopy) +{ + auto cfg = getCreateConfig(); + auto grp = cfg->GetGroup("TopLevelGroup"); + auto sub2 = grp->GetGroup("Sub1/Sub2"); + sub2->SetFloat("Parameter", 1.5); + + auto sub3 = grp->GetGroup("Sub3"); + sub3->SetFloat("AnotherParameter", 2.5); + sub2->copyTo(sub3); + EXPECT_TRUE(sub3->GetFloats("Test").empty()); + EXPECT_EQ(sub3->GetFloats().size(), 1); + EXPECT_EQ(sub3->GetFloats().at(0), 1.5); + + // Test that old parameter has been removed + EXPECT_TRUE(sub3->GetFloats("AnotherParameter").empty()); +} + +TEST_F(ParameterTest, TestInsert) +{ + auto cfg = getCreateConfig(); + auto grp = cfg->GetGroup("TopLevelGroup"); + auto sub2 = grp->GetGroup("Sub1/Sub2"); + sub2->SetFloat("Parameter", 1.5); + + auto sub3 = grp->GetGroup("Sub3"); + sub3->SetFloat("AnotherParameter", 2.5); + sub2->insertTo(sub3); + + EXPECT_EQ(sub3->GetFloats().size(), 2); + EXPECT_EQ(sub3->GetFloats("AnotherParameter").size(), 1); +} + +TEST_F(ParameterTest, TestRevert) +{ + auto cfg = getCreateConfig(); + auto grp = cfg->GetGroup("TopLevelGroup"); + auto sub1 = grp->GetGroup("Sub1/Sub/Sub"); + sub1->SetFloat("Float", 1.5); + + auto sub2 = grp->GetGroup("Sub2/Sub/Sub"); + sub2->SetFloat("Float", 1.5); + + grp->GetGroup("Sub1")->revert(grp->GetGroup("Sub2")); + + EXPECT_EQ(sub1->GetFloat("Float", 0.0), 0.0); + EXPECT_EQ(sub1->GetFloat("Float", 2.0), 2.0); + EXPECT_EQ(sub2->GetFloat("Float", 2.0), 1.5); +} + +TEST_F(ParameterTest, TestRemoveGroup) +{ + auto cfg = getCreateConfig(); + auto grp = cfg->GetGroup("TopLevelGroup"); + auto sub1 = grp->GetGroup("Sub1"); + auto sub2 = sub1->GetGroup("Sub2/Sub/Sub"); + sub2->SetFloat("Float", 1.5); + sub1->RemoveGrp("Sub2"); + sub2->SetInt("Int", 2); + EXPECT_EQ(sub2->GetInt("Int", 0), 2); + EXPECT_EQ(sub2->GetInt("Int", 1), 2); + cfg->CheckDocument(); +} + +TEST_F(ParameterTest, TestRenameGroup) +{ + auto cfg = getCreateConfig(); + auto grp = cfg->GetGroup("TopLevelGroup"); + auto sub1 = grp->GetGroup("Sub1"); + auto sub2 = sub1->GetGroup("Sub2/Sub/Sub"); + sub2->SetFloat("Float", 1.5); + sub1->RenameGrp("Sub2", "Sub3"); + sub2->SetInt("Int", 2); + EXPECT_EQ(sub2->GetInt("Int", 0), 2); + EXPECT_EQ(sub2->GetInt("Int", 1), 2); + cfg->CheckDocument(); +} + +// NOLINTEND(cppcoreguidelines-*,readability-*) From b02ec8dc670f4ed91d1ade5c5441262c782b57ba Mon Sep 17 00:00:00 2001 From: wmayer Date: Sat, 13 Apr 2024 12:47:20 +0200 Subject: [PATCH 13/24] Gui: fix crash in PythonCommand::isChecked() This very likely fixes the crash reported at https://forum.freecad.org/viewtopic.php?t=86874 --- src/Gui/Command.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Gui/Command.cpp b/src/Gui/Command.cpp index e263ddc48e..750cd4fbf0 100644 --- a/src/Gui/Command.cpp +++ b/src/Gui/Command.cpp @@ -1462,6 +1462,7 @@ bool PythonCommand::isCheckable() const bool PythonCommand::isChecked() const { + Base::PyGILStateLocker lock; PyObject* item = PyDict_GetItemString(_pcPyResourceDict,"Checkable"); if (!item) { throw Base::ValueError("PythonCommand::isChecked(): Method GetResources() of the Python " From a3d72167b6fca687273d3688cf7dd729ec4386f4 Mon Sep 17 00:00:00 2001 From: luzpaz Date: Sat, 13 Apr 2024 08:08:22 -0400 Subject: [PATCH 14/24] TD: Remove incorrect TODO Closes #13410 --- src/Mod/TechDraw/App/DrawUtil.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/TechDraw/App/DrawUtil.cpp b/src/Mod/TechDraw/App/DrawUtil.cpp index 5eb2562c04..0a35130312 100644 --- a/src/Mod/TechDraw/App/DrawUtil.cpp +++ b/src/Mod/TechDraw/App/DrawUtil.cpp @@ -120,7 +120,7 @@ std::string DrawUtil::getGeomTypeFromName(const std::string& geomName) std::stringstream ErrorMsg; if (boost::regex_search(begin, end, what, re, flags)) { - return what.str();//TODO: use std::stoi() in c++11 + return what.str(); } else { ErrorMsg << "In getGeomTypeFromName: malformed geometry name - " << geomName; throw Base::ValueError(ErrorMsg.str()); From 88b0ec51271e515d13f4c35d0109b56b70831152 Mon Sep 17 00:00:00 2001 From: Roy-043 Date: Fri, 12 Apr 2024 22:33:07 +0200 Subject: [PATCH 15/24] Gui: spelling: freezed -> frozen --- src/Gui/CommandFeat.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gui/CommandFeat.cpp b/src/Gui/CommandFeat.cpp index 30b65ee3a7..0b793a54ef 100644 --- a/src/Gui/CommandFeat.cpp +++ b/src/Gui/CommandFeat.cpp @@ -149,7 +149,7 @@ StdCmdToggleFreeze::StdCmdToggleFreeze() sGroup = "File"; sMenuText = QT_TR_NOOP("Toggle freeze"); static std::string toolTip = std::string("

") - + QT_TR_NOOP("Toggles freeze state of the selected objects. A freezed object is not recomputed when its parents change.") + + QT_TR_NOOP("Toggles freeze state of the selected objects. A frozen object is not recomputed when its parents change.") + "

"; sToolTipText = toolTip.c_str(); sStatusTip = sToolTipText; From 62910789397090949a084d9796ddbc74a7fbe0d8 Mon Sep 17 00:00:00 2001 From: bgbsww Date: Sat, 13 Apr 2024 19:50:20 -0400 Subject: [PATCH 16/24] Toponaming/Part: Initialize the hasher so that it operates --- src/Mod/Part/App/TopoShapeExpansion.cpp | 3 ++ .../TestTopologicalNamingProblem.py | 5 ++ tests/src/Mod/Part/App/FeatureOffset.cpp | 53 ++++++------------- 3 files changed, 23 insertions(+), 38 deletions(-) diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index 556560ea22..d8bcd46ab3 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -158,6 +158,9 @@ Data::ElementMapPtr TopoShape::resetElementMap(Data::ElementMapPtr elementMap) _cache->subLocation.Identity(); _subLocation.Identity(); _parentCache.reset(); + if ( ! elementMap->hasher && Hasher ) { + elementMap->hasher = Hasher; + } } return Data::ComplexGeoData::resetElementMap(elementMap); } diff --git a/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py b/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py index 22ed6d4f98..4b33f89409 100644 --- a/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py +++ b/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py @@ -417,6 +417,11 @@ class TestTopologicalNamingProblem(unittest.TestCase): # Assert self.assertEqual(len(body.Shape.childShapes()), 1) self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 26) + self.assertEqual(body.Shape.ElementMapSize,30) + self.assertEqual(sketch.Shape.ElementMapSize,12) + self.assertEqual(pad.Shape.ElementMapSize,30) + self.assertEqual(pad.Shape.childShapes()[0].ElementMapSize,30) + # Todo: Assert that the names in the ElementMap are good; in particular that they are hashed with a # starting def testPartDesignElementMapRevolution(self): # Arrange diff --git a/tests/src/Mod/Part/App/FeatureOffset.cpp b/tests/src/Mod/Part/App/FeatureOffset.cpp index d8d9b463cb..22d2eb996e 100644 --- a/tests/src/Mod/Part/App/FeatureOffset.cpp +++ b/tests/src/Mod/Part/App/FeatureOffset.cpp @@ -83,44 +83,21 @@ TEST_F(FeatureOffsetTest, testOffset3DWithExistingElementMap) EXPECT_TRUE(boxesMatch(bb, Base::BoundBox3d(-2, -2, -2, 3, 5, 5))); // Assert correct element Map #ifdef FC_USE_TNP_FIX - EXPECT_TRUE(elementsMatch( - _offset->Shape.getShape(), - { - "Edge10;FUS;:He59:4,E;RFI;:He59:4,E;:G;OFS;:He60:7,E;SLD;:He60:4,E", - "Edge11;FUS;:He5a:4,E;RFI;:He5a:4,E;:G;OFS;:He60:7,E;SLD;:He60:4,E", - "Edge12;FUS;:He5a:4,E;RFI;:He5a:4,E;:G;OFS;:He60:7,E;SLD;:He60:4,E", - "Edge1;FUS;:He59:4,E;RFI;:He59:4,E;:G;OFS;:He60:7,E;SLD;:He60:4,E", - "Edge2;:M2(Edge2;:He5a,E);FUS;:He59:17,E;:G(Edge2;:M2;FUS;:He5a:8,E;K-1;:He5a:4,E|" - "Edge2;:M;FUS;:He59:7,E;K-1;:He59:4,E);RFI;:He59:53,E;:G;OFS;:He60:7,E;SLD;:He60:4,E", - "Edge3;FUS;:He5a:4,E;RFI;:He5a:4,E;:G;OFS;:He60:7,E;SLD;:He60:4,E", - "Edge4;:M2(Edge4;:He5a,E);FUS;:He59:17,E;:G(Edge4;:M2;FUS;:He5a:8,E;K-1;:He5a:4,E|" - "Edge4;:M;FUS;:He59:7,E;K-1;:He59:4,E);RFI;:He59:53,E;:G;OFS;:He60:7,E;SLD;:He60:4,E", - "Edge5;FUS;:He59:4,E;RFI;:He59:4,E;:G;OFS;:He60:7,E;SLD;:He60:4,E", - "Edge6;:M2(Edge6;:He5a,E);FUS;:He59:17,E;:G(Edge6;:M2;FUS;:He5a:8,E;K-1;:He5a:4,E|" - "Edge6;:M;FUS;:He59:7,E;K-1;:He59:4,E);RFI;:He59:53,E;:G;OFS;:He60:7,E;SLD;:He60:4,E", - "Edge7;FUS;:He5a:4,E;RFI;:He5a:4,E;:G;OFS;:He60:7,E;SLD;:He60:4,E", - "Edge8;:M2(Edge8;:He5a,E);FUS;:He59:17,E;:G(Edge8;:M2;FUS;:He5a:8,E;K-1;:He5a:4,E|" - "Edge8;:M;FUS;:He59:7,E;K-1;:He59:4,E);RFI;:He59:53,E;:G;OFS;:He60:7,E;SLD;:He60:4,E", - "Edge9;FUS;:He59:4,E;RFI;:He59:4,E;:G;OFS;:He60:7,E;SLD;:He60:4,E", - "Face1;:M2(Face1;:He5a,F);FUS;:He59:17,F;:G(Face1;:M2;FUS;:He5a:8,F;K-1;:He5a:4,F|" - "Face1;:M;FUS;:He59:7,F;K-1;:He59:4,F);RFI;:He59:53,F;:G;OFS;:He60:7,F;SLD;:He60:4,F", - "Face2;:M2(Face2;:He5a,F);FUS;:He59:17,F;:G(Face2;:M2;FUS;:He5a:8,F;K-1;:He5a:4,F|" - "Face2;:M;FUS;:He59:7,F;K-1;:He59:4,F);RFI;:He59:53,F;:G;OFS;:He60:7,F;SLD;:He60:4,F", - "Face3;FUS;:He59:4,F;RFI;:He59:4,F;:G;OFS;:He60:7,F;SLD;:He60:4,F", - "Face4;FUS;:He5a:4,F;RFI;:He5a:4,F;:G;OFS;:He60:7,F;SLD;:He60:4,F", - "Face5;:M2(Face5;:He5a,F);FUS;:He59:17,F;:G(Face5;:M2;FUS;:He5a:8,F;K-1;:He5a:4,F|" - "Face5;:M;FUS;:He59:7,F;K-1;:He59:4,F);RFI;:He59:53,F;:G;OFS;:He60:7,F;SLD;:He60:4,F", - "Face6;:M2(Face6;:He5a,F);FUS;:He59:17,F;:G(Face6;:M2;FUS;:He5a:8,F;K-1;:He5a:4,F|" - "Face6;:M;FUS;:He59:7,F;K-1;:He59:4,F);RFI;:He59:53,F;:G;OFS;:He60:7,F;SLD;:He60:4,F", - "Vertex1;FUS;:He59:4,V;RFI;:He59:4,V;:G;OFS;:He60:7,V;SLD;:He60:4,V", - "Vertex2;FUS;:He59:4,V;RFI;:He59:4,V;:G;OFS;:He60:7,V;SLD;:He60:4,V", - "Vertex3;FUS;:He5a:4,V;RFI;:He5a:4,V;:G;OFS;:He60:7,V;SLD;:He60:4,V", - "Vertex4;FUS;:He5a:4,V;RFI;:He5a:4,V;:G;OFS;:He60:7,V;SLD;:He60:4,V", - "Vertex5;FUS;:He59:4,V;RFI;:He59:4,V;:G;OFS;:He60:7,V;SLD;:He60:4,V", - "Vertex6;FUS;:He59:4,V;RFI;:He59:4,V;:G;OFS;:He60:7,V;SLD;:He60:4,V", - "Vertex7;FUS;:He5a:4,V;RFI;:He5a:4,V;:G;OFS;:He60:7,V;SLD;:He60:4,V", - "Vertex8;FUS;:He5a:4,V;RFI;:He5a:4,V;:G;OFS;:He60:7,V;SLD;:He60:4,V", - })); + EXPECT_TRUE( + elementsMatch(_offset->Shape.getShape(), + { + "#21:3;SLD;:H1d7:4,V", "#21:4;SLD;:H1d7:4,V", "#21:7;SLD;:H1d7:4,V", + "#21:8;SLD;:H1d7:4,V", "#22:1;SLD;:H1d7:4,V", "#22:2;SLD;:H1d7:4,V", + "#22:5;SLD;:H1d7:4,V", "#22:6;SLD;:H1d7:4,V", "#24:3;SLD;:H1d7:4,E", + "#24:7;SLD;:H1d7:4,E", "#24:b;SLD;:H1d7:4,E", "#24:c;SLD;:H1d7:4,E", + "#25:2;SLD;:H1d7:4,E", "#26:4;SLD;:H1d7:4,E", "#27:1;SLD;:H1d7:4,E", + "#27:5;SLD;:H1d7:4,E", "#27:9;SLD;:H1d7:4,E", "#27:a;SLD;:H1d7:4,E", + "#28:6;SLD;:H1d7:4,E", "#29:8;SLD;:H1d7:4,E", "#2b:4;SLD;:H1d7:4,F", + "#2c:1;SLD;:H1d7:4,F", "#2d:3;SLD;:H1d7:4,F", "#2e:6;SLD;:H1d7:4,F", + "#2f:5;SLD;:H1d7:4,F", "#30:2;SLD;:H1d7:4,F", + })); + + #else EXPECT_EQ(_offset->Shape.getShape().getElementMapSize(), 0); #endif From 2377175ab189dd8c6c334a1e058ba1be986cf418 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Wed, 10 Apr 2024 10:29:57 -0400 Subject: [PATCH 17/24] Toponaming/Part: Transfer in python ElementMapVersion --- src/App/DocumentObject.cpp | 18 ++++++++++++++++++ src/App/DocumentObject.h | 14 ++++++++++++++ src/App/DocumentObjectPy.xml | 7 +++++++ src/App/DocumentObjectPyImp.cpp | 16 ++++++++++++++++ src/App/GeoFeaturePy.xml | 6 ++++++ src/App/GeoFeaturePyImp.cpp | 5 +++++ 6 files changed, 66 insertions(+) diff --git a/src/App/DocumentObject.cpp b/src/App/DocumentObject.cpp index 5fa4e91c93..ad2bb4bacb 100644 --- a/src/App/DocumentObject.cpp +++ b/src/App/DocumentObject.cpp @@ -1304,6 +1304,24 @@ bool DocumentObject::adjustRelativeLinks( return touched; } +std::string DocumentObject::getElementMapVersion(const App::Property* _prop, bool restored) const +{ + auto prop = Base::freecad_dynamic_cast(_prop); + if (!prop) { + return std::string(); + } + return prop->getElementMapVersion(restored); +} + +bool DocumentObject::checkElementMapVersion(const App::Property* _prop, const char* ver) const +{ + auto prop = Base::freecad_dynamic_cast(_prop); + if (!prop) { + return false; + } + return prop->checkElementMapVersion(ver); +} + const std::string &DocumentObject::hiddenMarker() { static std::string marker("!hide"); return marker; diff --git a/src/App/DocumentObject.h b/src/App/DocumentObject.h index 4cc24107d3..7052f92d7e 100644 --- a/src/App/DocumentObject.h +++ b/src/App/DocumentObject.h @@ -300,6 +300,20 @@ public: bool testIfLinkDAGCompatible(App::PropertyLinkSubList &linksTo) const; bool testIfLinkDAGCompatible(App::PropertyLinkSub &linkTo) const; + /** Return the element map version of the geometry data stored in the given property + * + * @param prop: the geometry property to query for element map version + * @param restored: whether to query for the restored element map version. + * In case of version upgrade, the restored version may + * be different from the current version. + * + * @return Return the element map version string. + */ + virtual std::string getElementMapVersion(const App::Property *prop, bool restored=false) const; + + /// Return true to signal re-generation of geometry element names + virtual bool checkElementMapVersion(const App::Property *prop, const char *ver) const; + public: /** mustExecute * We call this method to check if the object was modified to diff --git a/src/App/DocumentObjectPy.xml b/src/App/DocumentObjectPy.xml index 8792376ad9..7424fc9a58 100644 --- a/src/App/DocumentObjectPy.xml +++ b/src/App/DocumentObjectPy.xml @@ -225,6 +225,13 @@ Return tuple(obj,newElementName,oldElementName) adjustRelativeLinks(parent,recursive=True) -- auto correct potential cyclic dependencies + + + + getElementMapVersion(property_name): return element map version of a given geometry property + + + A list of all objects this object links to. diff --git a/src/App/DocumentObjectPyImp.cpp b/src/App/DocumentObjectPyImp.cpp index 4b3282a946..07344d2e93 100644 --- a/src/App/DocumentObjectPyImp.cpp +++ b/src/App/DocumentObjectPyImp.cpp @@ -745,6 +745,22 @@ PyObject* DocumentObjectPy::getPathsByOutList(PyObject *args) } } +PyObject* DocumentObjectPy::getElementMapVersion(PyObject* args) +{ + const char* name; + PyObject* restored = Py_False; + if (!PyArg_ParseTuple(args, "s|O", &name, &restored)) { + return NULL; + } + + Property* prop = getDocumentObjectPtr()->getPropertyByName(name); + if (!prop) { + throw Py::ValueError("property not found"); + } + return Py::new_reference_to( + Py::String(getDocumentObjectPtr()->getElementMapVersion(prop, Base::asBoolean(restored)))); +} + PyObject *DocumentObjectPy::getCustomAttributes(const char* ) const { return nullptr; diff --git a/src/App/GeoFeaturePy.xml b/src/App/GeoFeaturePy.xml index 83726bf70a..aeaefe7a4e 100644 --- a/src/App/GeoFeaturePy.xml +++ b/src/App/GeoFeaturePy.xml @@ -58,6 +58,12 @@ If an object has no such property then None is returned. Unlike to getPropertyNameOfGeometry this function returns the geometry, not its name. + + + Element map version + + + diff --git a/src/App/GeoFeaturePyImp.cpp b/src/App/GeoFeaturePyImp.cpp index ec2af688df..26a08a9f2d 100644 --- a/src/App/GeoFeaturePyImp.cpp +++ b/src/App/GeoFeaturePyImp.cpp @@ -93,3 +93,8 @@ int GeoFeaturePy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) { return 0; } + +Py::String GeoFeaturePy::getElementMapVersion() const { + return Py::String(getGeoFeaturePtr()->getElementMapVersion( + getGeoFeaturePtr()->getPropertyOfGeometry())); +} From 2eb4689996e83e33cf8621384d1ffebc76c37672 Mon Sep 17 00:00:00 2001 From: bgbsww Date: Wed, 10 Apr 2024 10:30:31 -0400 Subject: [PATCH 18/24] Toponaming/Part: Add Pad test, cleanup --- .../TestTopologicalNamingProblem.py | 75 +++++++++++++------ 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py b/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py index 4b33f89409..be2c42b789 100644 --- a/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py +++ b/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py @@ -110,11 +110,38 @@ class TestTopologicalNamingProblem(unittest.TestCase): else: print("TOPOLOGICAL NAMING PROBLEM IS PRESENT.") + def testPartDesignElementMapPad(self): + """ Test that padding a sketch results in a correct element map. Note that comprehensive testing + of the geometric functionality of the Pad is in TestPad.py """ + # Arrange + body = self.Doc.addObject('PartDesign::Body', 'Body') + padSketch = self.Doc.addObject('Sketcher::SketchObject', 'SketchPad') + pad = self.Doc.addObject("PartDesign::Pad", "Pad") + body.addObject(padSketch) + body.addObject(pad) + TestSketcherApp.CreateRectangleSketch(padSketch, (0, 0), (1, 1)) + pad.Profile = padSketch + pad.Length = 1 + # Act + self.Doc.recompute() + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. + return + reverseMap = pad.Shape.ElementReverseMap + faces = [name for name in reverseMap.keys() if name.startswith("Face")] + edges = [name for name in reverseMap.keys() if name.startswith("Edge")] + vertexes = [name for name in reverseMap.keys() if name.startswith("Vertex")] + # Assert + self.assertEqual(pad.Shape.ElementMapSize,30) # 4 duplicated Vertexes in here + self.assertEqual(len(reverseMap),26) + self.assertEqual(len(faces),6) + self.assertEqual(len(edges),12) + self.assertEqual(len(vertexes),8) + def testPartDesignElementMapBox(self): # Arrange body = self.Doc.addObject('PartDesign::Body', 'Body') box = self.Doc.addObject('PartDesign::AdditiveBox', 'Box') - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act / Assert self.assertEqual(len(box.Shape.childShapes()), 0) @@ -131,7 +158,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): # Arrange body = self.Doc.addObject('PartDesign::Body', 'Body') cylinder = self.Doc.addObject('PartDesign::AdditiveCylinder', 'Cylinder') - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act / Assert self.assertEqual(len(cylinder.Shape.childShapes()), 0) @@ -148,7 +175,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): # Arrange body = self.Doc.addObject('PartDesign::Body', 'Body') sphere = self.Doc.addObject('PartDesign::AdditiveSphere', 'Sphere') - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act / Assert self.assertEqual(len(sphere.Shape.childShapes()), 0) @@ -165,7 +192,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): # Arrange body = self.Doc.addObject('PartDesign::Body', 'Body') cone = self.Doc.addObject('PartDesign::AdditiveCone', 'Cone') - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act / Assert self.assertEqual(len(cone.Shape.childShapes()), 0) @@ -182,7 +209,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): # Arrange body = self.Doc.addObject('PartDesign::Body', 'Body') ellipsoid = self.Doc.addObject('PartDesign::AdditiveEllipsoid', 'Ellipsoid') - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act / Assert self.assertEqual(len(ellipsoid.Shape.childShapes()), 0) @@ -199,7 +226,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): # Arrange body = self.Doc.addObject('PartDesign::Body', 'Body') torus = self.Doc.addObject('PartDesign::AdditiveTorus', 'Torus') - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act / Assert self.assertEqual(len(torus.Shape.childShapes()), 0) @@ -216,7 +243,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): # Arrange body = self.Doc.addObject('PartDesign::Body', 'Body') prism = self.Doc.addObject('PartDesign::AdditivePrism', 'Prism') - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act / Assert self.assertEqual(len(prism.Shape.childShapes()), 0) @@ -233,7 +260,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): # Arrange body = self.Doc.addObject('PartDesign::Body', 'Body') wedge = self.Doc.addObject('PartDesign::AdditiveWedge', 'Wedge') - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act / Assert self.assertEqual(len(wedge.Shape.childShapes()), 0) @@ -256,7 +283,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): box.Width = 20 box.Height = 20 body.addObject(box) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act subbox = self.Doc.addObject('PartDesign::SubtractiveBox', 'Box') @@ -275,7 +302,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): box.Width = 20 box.Height = 20 body.addObject(box) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act subcylinder = self.Doc.addObject('PartDesign::SubtractiveCylinder', 'Cylinder') @@ -294,7 +321,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): box.Width = 20 box.Height = 20 body.addObject(box) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act subsphere = self.Doc.addObject('PartDesign::SubtractiveSphere', 'Sphere') @@ -313,7 +340,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): box.Width = 20 box.Height = 20 body.addObject(box) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act subcone = self.Doc.addObject('PartDesign::SubtractiveCone', 'Cone') @@ -332,7 +359,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): box.Width = 20 box.Height = 20 body.addObject(box) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act subellipsoid = self.Doc.addObject('PartDesign::SubtractiveEllipsoid', 'Ellipsoid') @@ -351,7 +378,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): box.Width = 20 box.Height = 20 body.addObject(box) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act subtorus = self.Doc.addObject('PartDesign::SubtractiveTorus', 'Torus') @@ -370,7 +397,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): box.Width = 20 box.Height = 20 body.addObject(box) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act subprism = self.Doc.addObject('PartDesign::SubtractivePrism', 'Prism') @@ -389,7 +416,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): box.Width = 20 box.Height = 20 body.addObject(box) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act subwedge = self.Doc.addObject('PartDesign::SubtractiveWedge', 'Wedge') @@ -405,7 +432,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): body = self.Doc.addObject('PartDesign::Body', 'Body') sketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch') TestSketcherApp.CreateRectangleSketch(sketch, (0, 0), (1, 1)) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act pad = self.Doc.addObject('PartDesign::Pad', 'Pad') @@ -428,7 +455,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): body = self.Doc.addObject('PartDesign::Body', 'Body') sketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch') TestSketcherApp.CreateRectangleSketch(sketch, (0, 0), (1, 1)) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act revolution = self.Doc.addObject('PartDesign::Revolution', 'Revolution') @@ -449,7 +476,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): sketch2 = self.Doc.addObject('Sketcher::SketchObject', 'Sketch') TestSketcherApp.CreateRectangleSketch(sketch2, (0, 0), (2, 2)) sketch2.Placement.move(App.Vector(0, 0, 3)) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act loft = self.Doc.addObject('PartDesign::AdditiveLoft', 'Loft') @@ -470,7 +497,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): TestSketcherApp.CreateRectangleSketch(sketch, (0, 0), (1, 1)) sketch2 = self.Doc.addObject('Sketcher::SketchObject', 'Sketch') TestSketcherApp.CreateRectangleSketch(sketch2, (0, 0), (2, 2)) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act pipe = self.Doc.addObject('PartDesign::AdditivePipe', 'Pipe') @@ -489,7 +516,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): body = self.Doc.addObject('PartDesign::Body', 'Body') sketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch') TestSketcherApp.CreateRectangleSketch(sketch, (0, 0), (1, 1)) - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act helix = self.Doc.addObject('PartDesign::AdditiveHelix', 'Helix') @@ -524,6 +551,8 @@ class TestTopologicalNamingProblem(unittest.TestCase): groove.Reversed = 0 groove.Base = App.Vector(0, 0, 0) self.Doc.recompute() + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. + return # Assert # print(groove.Shape.childShapes()[0].ElementMap) # TODO: Complete me as part of the subtractive features @@ -548,7 +577,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): pad.Profile = sketch body.addObject(pad) self.Doc.recompute() - if not hasattr(body,"ElementMapVersion"): # Skip without element maps. + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Assert self.assertEqual(sketch.Shape.ElementMapSize, 12) @@ -567,7 +596,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): pad = self.Doc.addObject('PartDesign::Pad', 'Pad') pad.Profile = plane self.Doc.recompute() - if not hasattr(pad,"ElementMapVersion"): # Skip without element maps. + if pad.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Assert self.assertEqual(plane.Shape.ElementMapSize, 0) From 9a592a64af4af270ea050d02d0b849b8f214c071 Mon Sep 17 00:00:00 2001 From: David Planella Date: Tue, 9 Apr 2024 10:18:20 +0200 Subject: [PATCH 19/24] Move all existing translation folders into locale subfolder --- src/Mod/TechDraw/Templates/{ => locale}/AR_IRAM/A0.svg | 0 src/Mod/TechDraw/Templates/{ => locale}/AR_IRAM/A1.svg | 0 src/Mod/TechDraw/Templates/{ => locale}/AR_IRAM/A2.svg | 0 src/Mod/TechDraw/Templates/{ => locale}/AR_IRAM/A3.svg | 0 src/Mod/TechDraw/Templates/{ => locale}/AR_IRAM/A4.svg | 0 src/Mod/TechDraw/Templates/{ => locale}/AR_IRAM/LEEME | 0 .../Templates/{ => locale}/DE/A0_Landscape_ISO7200_DE.svg | 0 .../Templates/{ => locale}/DE/A1_Landscape_ISO7200_DE.svg | 0 .../Templates/{ => locale}/DE/A2_Landscape_ISO7200_DE.svg | 0 .../Templates/{ => locale}/DE/A3_Landscape_ISO7200_DE.svg | 0 .../Templates/{ => locale}/DE/A4_Landscape_ISO7200_DE.svg | 0 .../TechDraw/Templates/{ => locale}/DE/A4_Portrait_ISO7200_DE.svg | 0 .../Templates/{ => locale}/RU_GOST/Leading/Landscape_A0.svg | 0 .../Templates/{ => locale}/RU_GOST/Leading/Landscape_A1.svg | 0 .../Templates/{ => locale}/RU_GOST/Leading/Landscape_A2.svg | 0 .../Templates/{ => locale}/RU_GOST/Leading/Landscape_A3.svg | 0 .../{ => locale}/RU_GOST/Leading/Landscape_A4_NotInGOST.svg | 0 .../Templates/{ => locale}/RU_GOST/Leading/Portrait_A0.svg | 0 .../Templates/{ => locale}/RU_GOST/Leading/Portrait_A1.svg | 0 .../Templates/{ => locale}/RU_GOST/Leading/Portrait_A2.svg | 0 .../Templates/{ => locale}/RU_GOST/Leading/Portrait_A3.svg | 0 .../Templates/{ => locale}/RU_GOST/Leading/Portrait_A4.svg | 0 .../Templates/{ => locale}/RU_GOST/Leading_text/Portrait_A3.svg | 0 .../Templates/{ => locale}/RU_GOST/Leading_text/Portrait_A4.svg | 0 .../Templates/{ => locale}/RU_GOST/Subsequent/Landscape_A0.svg | 0 .../Templates/{ => locale}/RU_GOST/Subsequent/Landscape_A1.svg | 0 .../Templates/{ => locale}/RU_GOST/Subsequent/Landscape_A2.svg | 0 .../Templates/{ => locale}/RU_GOST/Subsequent/Landscape_A3.svg | 0 .../Templates/{ => locale}/RU_GOST/Subsequent/Landscape_A4.svg | 0 .../Templates/{ => locale}/RU_GOST/Subsequent/Portrait_A0.svg | 0 .../Templates/{ => locale}/RU_GOST/Subsequent/Portrait_A1.svg | 0 .../Templates/{ => locale}/RU_GOST/Subsequent/Portrait_A2.svg | 0 .../Templates/{ => locale}/RU_GOST/Subsequent/Portrait_A3.svg | 0 .../Templates/{ => locale}/RU_GOST/Subsequent/Portrait_A4.svg | 0 .../{ => locale}/zh_CN/landscape/A0_Landscape_CN_CLIP.svg | 0 .../{ => locale}/zh_CN/landscape/A0_Landscape_CN_NO_CLIP.svg | 0 .../{ => locale}/zh_CN/landscape/A1_Landscape_CN_CLIP.svg | 0 .../{ => locale}/zh_CN/landscape/A1_Landscape_CN_NO_CLIP.svg | 0 .../{ => locale}/zh_CN/landscape/A2_Landscape_CN_CLIP.svg | 0 .../{ => locale}/zh_CN/landscape/A2_Landscape_CN_NO_CLIP.svg | 0 .../{ => locale}/zh_CN/landscape/A3_Landscape_CN_CLIP.svg | 0 .../{ => locale}/zh_CN/landscape/A3_Landscape_CN_NO_CLIP.svg | 0 .../{ => locale}/zh_CN/landscape/A4_Landscape_CN_CLIP.svg | 0 .../{ => locale}/zh_CN/landscape/A4_Landscape_CN_NO_CLIP.svg | 0 src/Mod/TechDraw/Templates/{ => locale}/zh_CN/landscape/README | 0 .../Templates/{ => locale}/zh_CN/portrait/A3_Portrait_CN_CLIP.svg | 0 .../Templates/{ => locale}/zh_CN/portrait/A4_Portrait_CN_CLIP.svg | 0 47 files changed, 0 insertions(+), 0 deletions(-) rename src/Mod/TechDraw/Templates/{ => locale}/AR_IRAM/A0.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/AR_IRAM/A1.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/AR_IRAM/A2.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/AR_IRAM/A3.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/AR_IRAM/A4.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/AR_IRAM/LEEME (100%) rename src/Mod/TechDraw/Templates/{ => locale}/DE/A0_Landscape_ISO7200_DE.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/DE/A1_Landscape_ISO7200_DE.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/DE/A2_Landscape_ISO7200_DE.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/DE/A3_Landscape_ISO7200_DE.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/DE/A4_Landscape_ISO7200_DE.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/DE/A4_Portrait_ISO7200_DE.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/RU_GOST/Leading/Landscape_A0.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/RU_GOST/Leading/Landscape_A1.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/RU_GOST/Leading/Landscape_A2.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/RU_GOST/Leading/Landscape_A3.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/RU_GOST/Leading/Landscape_A4_NotInGOST.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/RU_GOST/Leading/Portrait_A0.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/RU_GOST/Leading/Portrait_A1.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/RU_GOST/Leading/Portrait_A2.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/RU_GOST/Leading/Portrait_A3.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/RU_GOST/Leading/Portrait_A4.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/RU_GOST/Leading_text/Portrait_A3.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/RU_GOST/Leading_text/Portrait_A4.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/RU_GOST/Subsequent/Landscape_A0.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/RU_GOST/Subsequent/Landscape_A1.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/RU_GOST/Subsequent/Landscape_A2.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/RU_GOST/Subsequent/Landscape_A3.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/RU_GOST/Subsequent/Landscape_A4.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/RU_GOST/Subsequent/Portrait_A0.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/RU_GOST/Subsequent/Portrait_A1.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/RU_GOST/Subsequent/Portrait_A2.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/RU_GOST/Subsequent/Portrait_A3.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/RU_GOST/Subsequent/Portrait_A4.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/zh_CN/landscape/A0_Landscape_CN_CLIP.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/zh_CN/landscape/A0_Landscape_CN_NO_CLIP.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/zh_CN/landscape/A1_Landscape_CN_CLIP.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/zh_CN/landscape/A1_Landscape_CN_NO_CLIP.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/zh_CN/landscape/A2_Landscape_CN_CLIP.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/zh_CN/landscape/A2_Landscape_CN_NO_CLIP.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/zh_CN/landscape/A3_Landscape_CN_CLIP.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/zh_CN/landscape/A3_Landscape_CN_NO_CLIP.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/zh_CN/landscape/A4_Landscape_CN_CLIP.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/zh_CN/landscape/A4_Landscape_CN_NO_CLIP.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/zh_CN/landscape/README (100%) rename src/Mod/TechDraw/Templates/{ => locale}/zh_CN/portrait/A3_Portrait_CN_CLIP.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale}/zh_CN/portrait/A4_Portrait_CN_CLIP.svg (100%) diff --git a/src/Mod/TechDraw/Templates/AR_IRAM/A0.svg b/src/Mod/TechDraw/Templates/locale/AR_IRAM/A0.svg similarity index 100% rename from src/Mod/TechDraw/Templates/AR_IRAM/A0.svg rename to src/Mod/TechDraw/Templates/locale/AR_IRAM/A0.svg diff --git a/src/Mod/TechDraw/Templates/AR_IRAM/A1.svg b/src/Mod/TechDraw/Templates/locale/AR_IRAM/A1.svg similarity index 100% rename from src/Mod/TechDraw/Templates/AR_IRAM/A1.svg rename to src/Mod/TechDraw/Templates/locale/AR_IRAM/A1.svg diff --git a/src/Mod/TechDraw/Templates/AR_IRAM/A2.svg b/src/Mod/TechDraw/Templates/locale/AR_IRAM/A2.svg similarity index 100% rename from src/Mod/TechDraw/Templates/AR_IRAM/A2.svg rename to src/Mod/TechDraw/Templates/locale/AR_IRAM/A2.svg diff --git a/src/Mod/TechDraw/Templates/AR_IRAM/A3.svg b/src/Mod/TechDraw/Templates/locale/AR_IRAM/A3.svg similarity index 100% rename from src/Mod/TechDraw/Templates/AR_IRAM/A3.svg rename to src/Mod/TechDraw/Templates/locale/AR_IRAM/A3.svg diff --git a/src/Mod/TechDraw/Templates/AR_IRAM/A4.svg b/src/Mod/TechDraw/Templates/locale/AR_IRAM/A4.svg similarity index 100% rename from src/Mod/TechDraw/Templates/AR_IRAM/A4.svg rename to src/Mod/TechDraw/Templates/locale/AR_IRAM/A4.svg diff --git a/src/Mod/TechDraw/Templates/AR_IRAM/LEEME b/src/Mod/TechDraw/Templates/locale/AR_IRAM/LEEME similarity index 100% rename from src/Mod/TechDraw/Templates/AR_IRAM/LEEME rename to src/Mod/TechDraw/Templates/locale/AR_IRAM/LEEME diff --git a/src/Mod/TechDraw/Templates/DE/A0_Landscape_ISO7200_DE.svg b/src/Mod/TechDraw/Templates/locale/DE/A0_Landscape_ISO7200_DE.svg similarity index 100% rename from src/Mod/TechDraw/Templates/DE/A0_Landscape_ISO7200_DE.svg rename to src/Mod/TechDraw/Templates/locale/DE/A0_Landscape_ISO7200_DE.svg diff --git a/src/Mod/TechDraw/Templates/DE/A1_Landscape_ISO7200_DE.svg b/src/Mod/TechDraw/Templates/locale/DE/A1_Landscape_ISO7200_DE.svg similarity index 100% rename from src/Mod/TechDraw/Templates/DE/A1_Landscape_ISO7200_DE.svg rename to src/Mod/TechDraw/Templates/locale/DE/A1_Landscape_ISO7200_DE.svg diff --git a/src/Mod/TechDraw/Templates/DE/A2_Landscape_ISO7200_DE.svg b/src/Mod/TechDraw/Templates/locale/DE/A2_Landscape_ISO7200_DE.svg similarity index 100% rename from src/Mod/TechDraw/Templates/DE/A2_Landscape_ISO7200_DE.svg rename to src/Mod/TechDraw/Templates/locale/DE/A2_Landscape_ISO7200_DE.svg diff --git a/src/Mod/TechDraw/Templates/DE/A3_Landscape_ISO7200_DE.svg b/src/Mod/TechDraw/Templates/locale/DE/A3_Landscape_ISO7200_DE.svg similarity index 100% rename from src/Mod/TechDraw/Templates/DE/A3_Landscape_ISO7200_DE.svg rename to src/Mod/TechDraw/Templates/locale/DE/A3_Landscape_ISO7200_DE.svg diff --git a/src/Mod/TechDraw/Templates/DE/A4_Landscape_ISO7200_DE.svg b/src/Mod/TechDraw/Templates/locale/DE/A4_Landscape_ISO7200_DE.svg similarity index 100% rename from src/Mod/TechDraw/Templates/DE/A4_Landscape_ISO7200_DE.svg rename to src/Mod/TechDraw/Templates/locale/DE/A4_Landscape_ISO7200_DE.svg diff --git a/src/Mod/TechDraw/Templates/DE/A4_Portrait_ISO7200_DE.svg b/src/Mod/TechDraw/Templates/locale/DE/A4_Portrait_ISO7200_DE.svg similarity index 100% rename from src/Mod/TechDraw/Templates/DE/A4_Portrait_ISO7200_DE.svg rename to src/Mod/TechDraw/Templates/locale/DE/A4_Portrait_ISO7200_DE.svg diff --git a/src/Mod/TechDraw/Templates/RU_GOST/Leading/Landscape_A0.svg b/src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Landscape_A0.svg similarity index 100% rename from src/Mod/TechDraw/Templates/RU_GOST/Leading/Landscape_A0.svg rename to src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Landscape_A0.svg diff --git a/src/Mod/TechDraw/Templates/RU_GOST/Leading/Landscape_A1.svg b/src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Landscape_A1.svg similarity index 100% rename from src/Mod/TechDraw/Templates/RU_GOST/Leading/Landscape_A1.svg rename to src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Landscape_A1.svg diff --git a/src/Mod/TechDraw/Templates/RU_GOST/Leading/Landscape_A2.svg b/src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Landscape_A2.svg similarity index 100% rename from src/Mod/TechDraw/Templates/RU_GOST/Leading/Landscape_A2.svg rename to src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Landscape_A2.svg diff --git a/src/Mod/TechDraw/Templates/RU_GOST/Leading/Landscape_A3.svg b/src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Landscape_A3.svg similarity index 100% rename from src/Mod/TechDraw/Templates/RU_GOST/Leading/Landscape_A3.svg rename to src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Landscape_A3.svg diff --git a/src/Mod/TechDraw/Templates/RU_GOST/Leading/Landscape_A4_NotInGOST.svg b/src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Landscape_A4_NotInGOST.svg similarity index 100% rename from src/Mod/TechDraw/Templates/RU_GOST/Leading/Landscape_A4_NotInGOST.svg rename to src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Landscape_A4_NotInGOST.svg diff --git a/src/Mod/TechDraw/Templates/RU_GOST/Leading/Portrait_A0.svg b/src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Portrait_A0.svg similarity index 100% rename from src/Mod/TechDraw/Templates/RU_GOST/Leading/Portrait_A0.svg rename to src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Portrait_A0.svg diff --git a/src/Mod/TechDraw/Templates/RU_GOST/Leading/Portrait_A1.svg b/src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Portrait_A1.svg similarity index 100% rename from src/Mod/TechDraw/Templates/RU_GOST/Leading/Portrait_A1.svg rename to src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Portrait_A1.svg diff --git a/src/Mod/TechDraw/Templates/RU_GOST/Leading/Portrait_A2.svg b/src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Portrait_A2.svg similarity index 100% rename from src/Mod/TechDraw/Templates/RU_GOST/Leading/Portrait_A2.svg rename to src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Portrait_A2.svg diff --git a/src/Mod/TechDraw/Templates/RU_GOST/Leading/Portrait_A3.svg b/src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Portrait_A3.svg similarity index 100% rename from src/Mod/TechDraw/Templates/RU_GOST/Leading/Portrait_A3.svg rename to src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Portrait_A3.svg diff --git a/src/Mod/TechDraw/Templates/RU_GOST/Leading/Portrait_A4.svg b/src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Portrait_A4.svg similarity index 100% rename from src/Mod/TechDraw/Templates/RU_GOST/Leading/Portrait_A4.svg rename to src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Portrait_A4.svg diff --git a/src/Mod/TechDraw/Templates/RU_GOST/Leading_text/Portrait_A3.svg b/src/Mod/TechDraw/Templates/locale/RU_GOST/Leading_text/Portrait_A3.svg similarity index 100% rename from src/Mod/TechDraw/Templates/RU_GOST/Leading_text/Portrait_A3.svg rename to src/Mod/TechDraw/Templates/locale/RU_GOST/Leading_text/Portrait_A3.svg diff --git a/src/Mod/TechDraw/Templates/RU_GOST/Leading_text/Portrait_A4.svg b/src/Mod/TechDraw/Templates/locale/RU_GOST/Leading_text/Portrait_A4.svg similarity index 100% rename from src/Mod/TechDraw/Templates/RU_GOST/Leading_text/Portrait_A4.svg rename to src/Mod/TechDraw/Templates/locale/RU_GOST/Leading_text/Portrait_A4.svg diff --git a/src/Mod/TechDraw/Templates/RU_GOST/Subsequent/Landscape_A0.svg b/src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Landscape_A0.svg similarity index 100% rename from src/Mod/TechDraw/Templates/RU_GOST/Subsequent/Landscape_A0.svg rename to src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Landscape_A0.svg diff --git a/src/Mod/TechDraw/Templates/RU_GOST/Subsequent/Landscape_A1.svg b/src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Landscape_A1.svg similarity index 100% rename from src/Mod/TechDraw/Templates/RU_GOST/Subsequent/Landscape_A1.svg rename to src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Landscape_A1.svg diff --git a/src/Mod/TechDraw/Templates/RU_GOST/Subsequent/Landscape_A2.svg b/src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Landscape_A2.svg similarity index 100% rename from src/Mod/TechDraw/Templates/RU_GOST/Subsequent/Landscape_A2.svg rename to src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Landscape_A2.svg diff --git a/src/Mod/TechDraw/Templates/RU_GOST/Subsequent/Landscape_A3.svg b/src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Landscape_A3.svg similarity index 100% rename from src/Mod/TechDraw/Templates/RU_GOST/Subsequent/Landscape_A3.svg rename to src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Landscape_A3.svg diff --git a/src/Mod/TechDraw/Templates/RU_GOST/Subsequent/Landscape_A4.svg b/src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Landscape_A4.svg similarity index 100% rename from src/Mod/TechDraw/Templates/RU_GOST/Subsequent/Landscape_A4.svg rename to src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Landscape_A4.svg diff --git a/src/Mod/TechDraw/Templates/RU_GOST/Subsequent/Portrait_A0.svg b/src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Portrait_A0.svg similarity index 100% rename from src/Mod/TechDraw/Templates/RU_GOST/Subsequent/Portrait_A0.svg rename to src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Portrait_A0.svg diff --git a/src/Mod/TechDraw/Templates/RU_GOST/Subsequent/Portrait_A1.svg b/src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Portrait_A1.svg similarity index 100% rename from src/Mod/TechDraw/Templates/RU_GOST/Subsequent/Portrait_A1.svg rename to src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Portrait_A1.svg diff --git a/src/Mod/TechDraw/Templates/RU_GOST/Subsequent/Portrait_A2.svg b/src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Portrait_A2.svg similarity index 100% rename from src/Mod/TechDraw/Templates/RU_GOST/Subsequent/Portrait_A2.svg rename to src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Portrait_A2.svg diff --git a/src/Mod/TechDraw/Templates/RU_GOST/Subsequent/Portrait_A3.svg b/src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Portrait_A3.svg similarity index 100% rename from src/Mod/TechDraw/Templates/RU_GOST/Subsequent/Portrait_A3.svg rename to src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Portrait_A3.svg diff --git a/src/Mod/TechDraw/Templates/RU_GOST/Subsequent/Portrait_A4.svg b/src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Portrait_A4.svg similarity index 100% rename from src/Mod/TechDraw/Templates/RU_GOST/Subsequent/Portrait_A4.svg rename to src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Portrait_A4.svg diff --git a/src/Mod/TechDraw/Templates/zh_CN/landscape/A0_Landscape_CN_CLIP.svg b/src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A0_Landscape_CN_CLIP.svg similarity index 100% rename from src/Mod/TechDraw/Templates/zh_CN/landscape/A0_Landscape_CN_CLIP.svg rename to src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A0_Landscape_CN_CLIP.svg diff --git a/src/Mod/TechDraw/Templates/zh_CN/landscape/A0_Landscape_CN_NO_CLIP.svg b/src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A0_Landscape_CN_NO_CLIP.svg similarity index 100% rename from src/Mod/TechDraw/Templates/zh_CN/landscape/A0_Landscape_CN_NO_CLIP.svg rename to src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A0_Landscape_CN_NO_CLIP.svg diff --git a/src/Mod/TechDraw/Templates/zh_CN/landscape/A1_Landscape_CN_CLIP.svg b/src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A1_Landscape_CN_CLIP.svg similarity index 100% rename from src/Mod/TechDraw/Templates/zh_CN/landscape/A1_Landscape_CN_CLIP.svg rename to src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A1_Landscape_CN_CLIP.svg diff --git a/src/Mod/TechDraw/Templates/zh_CN/landscape/A1_Landscape_CN_NO_CLIP.svg b/src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A1_Landscape_CN_NO_CLIP.svg similarity index 100% rename from src/Mod/TechDraw/Templates/zh_CN/landscape/A1_Landscape_CN_NO_CLIP.svg rename to src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A1_Landscape_CN_NO_CLIP.svg diff --git a/src/Mod/TechDraw/Templates/zh_CN/landscape/A2_Landscape_CN_CLIP.svg b/src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A2_Landscape_CN_CLIP.svg similarity index 100% rename from src/Mod/TechDraw/Templates/zh_CN/landscape/A2_Landscape_CN_CLIP.svg rename to src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A2_Landscape_CN_CLIP.svg diff --git a/src/Mod/TechDraw/Templates/zh_CN/landscape/A2_Landscape_CN_NO_CLIP.svg b/src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A2_Landscape_CN_NO_CLIP.svg similarity index 100% rename from src/Mod/TechDraw/Templates/zh_CN/landscape/A2_Landscape_CN_NO_CLIP.svg rename to src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A2_Landscape_CN_NO_CLIP.svg diff --git a/src/Mod/TechDraw/Templates/zh_CN/landscape/A3_Landscape_CN_CLIP.svg b/src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A3_Landscape_CN_CLIP.svg similarity index 100% rename from src/Mod/TechDraw/Templates/zh_CN/landscape/A3_Landscape_CN_CLIP.svg rename to src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A3_Landscape_CN_CLIP.svg diff --git a/src/Mod/TechDraw/Templates/zh_CN/landscape/A3_Landscape_CN_NO_CLIP.svg b/src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A3_Landscape_CN_NO_CLIP.svg similarity index 100% rename from src/Mod/TechDraw/Templates/zh_CN/landscape/A3_Landscape_CN_NO_CLIP.svg rename to src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A3_Landscape_CN_NO_CLIP.svg diff --git a/src/Mod/TechDraw/Templates/zh_CN/landscape/A4_Landscape_CN_CLIP.svg b/src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A4_Landscape_CN_CLIP.svg similarity index 100% rename from src/Mod/TechDraw/Templates/zh_CN/landscape/A4_Landscape_CN_CLIP.svg rename to src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A4_Landscape_CN_CLIP.svg diff --git a/src/Mod/TechDraw/Templates/zh_CN/landscape/A4_Landscape_CN_NO_CLIP.svg b/src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A4_Landscape_CN_NO_CLIP.svg similarity index 100% rename from src/Mod/TechDraw/Templates/zh_CN/landscape/A4_Landscape_CN_NO_CLIP.svg rename to src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A4_Landscape_CN_NO_CLIP.svg diff --git a/src/Mod/TechDraw/Templates/zh_CN/landscape/README b/src/Mod/TechDraw/Templates/locale/zh_CN/landscape/README similarity index 100% rename from src/Mod/TechDraw/Templates/zh_CN/landscape/README rename to src/Mod/TechDraw/Templates/locale/zh_CN/landscape/README diff --git a/src/Mod/TechDraw/Templates/zh_CN/portrait/A3_Portrait_CN_CLIP.svg b/src/Mod/TechDraw/Templates/locale/zh_CN/portrait/A3_Portrait_CN_CLIP.svg similarity index 100% rename from src/Mod/TechDraw/Templates/zh_CN/portrait/A3_Portrait_CN_CLIP.svg rename to src/Mod/TechDraw/Templates/locale/zh_CN/portrait/A3_Portrait_CN_CLIP.svg diff --git a/src/Mod/TechDraw/Templates/zh_CN/portrait/A4_Portrait_CN_CLIP.svg b/src/Mod/TechDraw/Templates/locale/zh_CN/portrait/A4_Portrait_CN_CLIP.svg similarity index 100% rename from src/Mod/TechDraw/Templates/zh_CN/portrait/A4_Portrait_CN_CLIP.svg rename to src/Mod/TechDraw/Templates/locale/zh_CN/portrait/A4_Portrait_CN_CLIP.svg From 830c5e7943e2ae4462c96985bd650eb12f44b72f Mon Sep 17 00:00:00 2001 From: David Planella Date: Tue, 9 Apr 2024 10:30:46 +0200 Subject: [PATCH 20/24] Remove stray translations into their own locale folder --- .../Templates/{A3_LandscapeTD.svg => A3_Landscape_TD.svg} | 0 .../Templates/{A3_Landscape_EN_m52.svg => A3_Landscape_m52.svg} | 0 .../TechDraw/Templates/{ => locale/fr}/A3_Landscape_FR_m52.svg | 0 .../TechDraw/Templates/{ => locale/it}/A3_Landscape_IT_m52.svg | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename src/Mod/TechDraw/Templates/{A3_LandscapeTD.svg => A3_Landscape_TD.svg} (100%) rename src/Mod/TechDraw/Templates/{A3_Landscape_EN_m52.svg => A3_Landscape_m52.svg} (100%) rename src/Mod/TechDraw/Templates/{ => locale/fr}/A3_Landscape_FR_m52.svg (100%) rename src/Mod/TechDraw/Templates/{ => locale/it}/A3_Landscape_IT_m52.svg (100%) diff --git a/src/Mod/TechDraw/Templates/A3_LandscapeTD.svg b/src/Mod/TechDraw/Templates/A3_Landscape_TD.svg similarity index 100% rename from src/Mod/TechDraw/Templates/A3_LandscapeTD.svg rename to src/Mod/TechDraw/Templates/A3_Landscape_TD.svg diff --git a/src/Mod/TechDraw/Templates/A3_Landscape_EN_m52.svg b/src/Mod/TechDraw/Templates/A3_Landscape_m52.svg similarity index 100% rename from src/Mod/TechDraw/Templates/A3_Landscape_EN_m52.svg rename to src/Mod/TechDraw/Templates/A3_Landscape_m52.svg diff --git a/src/Mod/TechDraw/Templates/A3_Landscape_FR_m52.svg b/src/Mod/TechDraw/Templates/locale/fr/A3_Landscape_FR_m52.svg similarity index 100% rename from src/Mod/TechDraw/Templates/A3_Landscape_FR_m52.svg rename to src/Mod/TechDraw/Templates/locale/fr/A3_Landscape_FR_m52.svg diff --git a/src/Mod/TechDraw/Templates/A3_Landscape_IT_m52.svg b/src/Mod/TechDraw/Templates/locale/it/A3_Landscape_IT_m52.svg similarity index 100% rename from src/Mod/TechDraw/Templates/A3_Landscape_IT_m52.svg rename to src/Mod/TechDraw/Templates/locale/it/A3_Landscape_IT_m52.svg From 34238a0b3dbcd66dc74258765de546bf75f60b01 Mon Sep 17 00:00:00 2001 From: David Planella Date: Tue, 9 Apr 2024 11:23:49 +0200 Subject: [PATCH 21/24] Renamed locale folders according to IETF subtags, added README file with instructions on how to add a new translation --- .../{A4_LandscapeTD.svg => A4_Landscape_TD.svg} | 0 src/Mod/TechDraw/Templates/locale/README.md | 15 +++++++++++++++ .../locale/{DE => de}/A0_Landscape_ISO7200_DE.svg | 0 .../locale/{DE => de}/A1_Landscape_ISO7200_DE.svg | 0 .../locale/{DE => de}/A2_Landscape_ISO7200_DE.svg | 0 .../locale/{DE => de}/A3_Landscape_ISO7200_DE.svg | 0 .../locale/{DE => de}/A4_Landscape_ISO7200_DE.svg | 0 .../locale/{DE => de}/A4_Portrait_ISO7200_DE.svg | 0 .../Templates/locale/{AR_IRAM => es-AR}/A0.svg | 0 .../Templates/locale/{AR_IRAM => es-AR}/A1.svg | 0 .../Templates/locale/{AR_IRAM => es-AR}/A2.svg | 0 .../Templates/locale/{AR_IRAM => es-AR}/A3.svg | 0 .../Templates/locale/{AR_IRAM => es-AR}/A4.svg | 0 .../Templates/locale/{AR_IRAM => es-AR}/LEEME | 0 .../{RU_GOST => ru}/Leading/Landscape_A0.svg | 0 .../{RU_GOST => ru}/Leading/Landscape_A1.svg | 0 .../{RU_GOST => ru}/Leading/Landscape_A2.svg | 0 .../{RU_GOST => ru}/Leading/Landscape_A3.svg | 0 .../Leading/Landscape_A4_NotInGOST.svg | 0 .../{RU_GOST => ru}/Leading/Portrait_A0.svg | 0 .../{RU_GOST => ru}/Leading/Portrait_A1.svg | 0 .../{RU_GOST => ru}/Leading/Portrait_A2.svg | 0 .../{RU_GOST => ru}/Leading/Portrait_A3.svg | 0 .../{RU_GOST => ru}/Leading/Portrait_A4.svg | 0 .../{RU_GOST => ru}/Leading_text/Portrait_A3.svg | 0 .../{RU_GOST => ru}/Leading_text/Portrait_A4.svg | 0 .../{RU_GOST => ru}/Subsequent/Landscape_A0.svg | 0 .../{RU_GOST => ru}/Subsequent/Landscape_A1.svg | 0 .../{RU_GOST => ru}/Subsequent/Landscape_A2.svg | 0 .../{RU_GOST => ru}/Subsequent/Landscape_A3.svg | 0 .../{RU_GOST => ru}/Subsequent/Landscape_A4.svg | 0 .../{RU_GOST => ru}/Subsequent/Portrait_A0.svg | 0 .../{RU_GOST => ru}/Subsequent/Portrait_A1.svg | 0 .../{RU_GOST => ru}/Subsequent/Portrait_A2.svg | 0 .../{RU_GOST => ru}/Subsequent/Portrait_A3.svg | 0 .../{RU_GOST => ru}/Subsequent/Portrait_A4.svg | 0 .../landscape/A0_Landscape_CN_CLIP.svg | 0 .../landscape/A0_Landscape_CN_NO_CLIP.svg | 0 .../landscape/A1_Landscape_CN_CLIP.svg | 0 .../landscape/A1_Landscape_CN_NO_CLIP.svg | 0 .../landscape/A2_Landscape_CN_CLIP.svg | 0 .../landscape/A2_Landscape_CN_NO_CLIP.svg | 0 .../landscape/A3_Landscape_CN_CLIP.svg | 0 .../landscape/A3_Landscape_CN_NO_CLIP.svg | 0 .../landscape/A4_Landscape_CN_CLIP.svg | 0 .../landscape/A4_Landscape_CN_NO_CLIP.svg | 0 .../locale/{zh_CN => zh-CN}/landscape/README | 0 .../portrait/A3_Portrait_CN_CLIP.svg | 0 .../portrait/A4_Portrait_CN_CLIP.svg | 0 49 files changed, 15 insertions(+) rename src/Mod/TechDraw/Templates/{A4_LandscapeTD.svg => A4_Landscape_TD.svg} (100%) create mode 100644 src/Mod/TechDraw/Templates/locale/README.md rename src/Mod/TechDraw/Templates/locale/{DE => de}/A0_Landscape_ISO7200_DE.svg (100%) rename src/Mod/TechDraw/Templates/locale/{DE => de}/A1_Landscape_ISO7200_DE.svg (100%) rename src/Mod/TechDraw/Templates/locale/{DE => de}/A2_Landscape_ISO7200_DE.svg (100%) rename src/Mod/TechDraw/Templates/locale/{DE => de}/A3_Landscape_ISO7200_DE.svg (100%) rename src/Mod/TechDraw/Templates/locale/{DE => de}/A4_Landscape_ISO7200_DE.svg (100%) rename src/Mod/TechDraw/Templates/locale/{DE => de}/A4_Portrait_ISO7200_DE.svg (100%) rename src/Mod/TechDraw/Templates/locale/{AR_IRAM => es-AR}/A0.svg (100%) rename src/Mod/TechDraw/Templates/locale/{AR_IRAM => es-AR}/A1.svg (100%) rename src/Mod/TechDraw/Templates/locale/{AR_IRAM => es-AR}/A2.svg (100%) rename src/Mod/TechDraw/Templates/locale/{AR_IRAM => es-AR}/A3.svg (100%) rename src/Mod/TechDraw/Templates/locale/{AR_IRAM => es-AR}/A4.svg (100%) rename src/Mod/TechDraw/Templates/locale/{AR_IRAM => es-AR}/LEEME (100%) rename src/Mod/TechDraw/Templates/locale/{RU_GOST => ru}/Leading/Landscape_A0.svg (100%) rename src/Mod/TechDraw/Templates/locale/{RU_GOST => ru}/Leading/Landscape_A1.svg (100%) rename src/Mod/TechDraw/Templates/locale/{RU_GOST => ru}/Leading/Landscape_A2.svg (100%) rename src/Mod/TechDraw/Templates/locale/{RU_GOST => ru}/Leading/Landscape_A3.svg (100%) rename src/Mod/TechDraw/Templates/locale/{RU_GOST => ru}/Leading/Landscape_A4_NotInGOST.svg (100%) rename src/Mod/TechDraw/Templates/locale/{RU_GOST => ru}/Leading/Portrait_A0.svg (100%) rename src/Mod/TechDraw/Templates/locale/{RU_GOST => ru}/Leading/Portrait_A1.svg (100%) rename src/Mod/TechDraw/Templates/locale/{RU_GOST => ru}/Leading/Portrait_A2.svg (100%) rename src/Mod/TechDraw/Templates/locale/{RU_GOST => ru}/Leading/Portrait_A3.svg (100%) rename src/Mod/TechDraw/Templates/locale/{RU_GOST => ru}/Leading/Portrait_A4.svg (100%) rename src/Mod/TechDraw/Templates/locale/{RU_GOST => ru}/Leading_text/Portrait_A3.svg (100%) rename src/Mod/TechDraw/Templates/locale/{RU_GOST => ru}/Leading_text/Portrait_A4.svg (100%) rename src/Mod/TechDraw/Templates/locale/{RU_GOST => ru}/Subsequent/Landscape_A0.svg (100%) rename src/Mod/TechDraw/Templates/locale/{RU_GOST => ru}/Subsequent/Landscape_A1.svg (100%) rename src/Mod/TechDraw/Templates/locale/{RU_GOST => ru}/Subsequent/Landscape_A2.svg (100%) rename src/Mod/TechDraw/Templates/locale/{RU_GOST => ru}/Subsequent/Landscape_A3.svg (100%) rename src/Mod/TechDraw/Templates/locale/{RU_GOST => ru}/Subsequent/Landscape_A4.svg (100%) rename src/Mod/TechDraw/Templates/locale/{RU_GOST => ru}/Subsequent/Portrait_A0.svg (100%) rename src/Mod/TechDraw/Templates/locale/{RU_GOST => ru}/Subsequent/Portrait_A1.svg (100%) rename src/Mod/TechDraw/Templates/locale/{RU_GOST => ru}/Subsequent/Portrait_A2.svg (100%) rename src/Mod/TechDraw/Templates/locale/{RU_GOST => ru}/Subsequent/Portrait_A3.svg (100%) rename src/Mod/TechDraw/Templates/locale/{RU_GOST => ru}/Subsequent/Portrait_A4.svg (100%) rename src/Mod/TechDraw/Templates/locale/{zh_CN => zh-CN}/landscape/A0_Landscape_CN_CLIP.svg (100%) rename src/Mod/TechDraw/Templates/locale/{zh_CN => zh-CN}/landscape/A0_Landscape_CN_NO_CLIP.svg (100%) rename src/Mod/TechDraw/Templates/locale/{zh_CN => zh-CN}/landscape/A1_Landscape_CN_CLIP.svg (100%) rename src/Mod/TechDraw/Templates/locale/{zh_CN => zh-CN}/landscape/A1_Landscape_CN_NO_CLIP.svg (100%) rename src/Mod/TechDraw/Templates/locale/{zh_CN => zh-CN}/landscape/A2_Landscape_CN_CLIP.svg (100%) rename src/Mod/TechDraw/Templates/locale/{zh_CN => zh-CN}/landscape/A2_Landscape_CN_NO_CLIP.svg (100%) rename src/Mod/TechDraw/Templates/locale/{zh_CN => zh-CN}/landscape/A3_Landscape_CN_CLIP.svg (100%) rename src/Mod/TechDraw/Templates/locale/{zh_CN => zh-CN}/landscape/A3_Landscape_CN_NO_CLIP.svg (100%) rename src/Mod/TechDraw/Templates/locale/{zh_CN => zh-CN}/landscape/A4_Landscape_CN_CLIP.svg (100%) rename src/Mod/TechDraw/Templates/locale/{zh_CN => zh-CN}/landscape/A4_Landscape_CN_NO_CLIP.svg (100%) rename src/Mod/TechDraw/Templates/locale/{zh_CN => zh-CN}/landscape/README (100%) rename src/Mod/TechDraw/Templates/locale/{zh_CN => zh-CN}/portrait/A3_Portrait_CN_CLIP.svg (100%) rename src/Mod/TechDraw/Templates/locale/{zh_CN => zh-CN}/portrait/A4_Portrait_CN_CLIP.svg (100%) diff --git a/src/Mod/TechDraw/Templates/A4_LandscapeTD.svg b/src/Mod/TechDraw/Templates/A4_Landscape_TD.svg similarity index 100% rename from src/Mod/TechDraw/Templates/A4_LandscapeTD.svg rename to src/Mod/TechDraw/Templates/A4_Landscape_TD.svg diff --git a/src/Mod/TechDraw/Templates/locale/README.md b/src/Mod/TechDraw/Templates/locale/README.md new file mode 100644 index 0000000000..45518445b4 --- /dev/null +++ b/src/Mod/TechDraw/Templates/locale/README.md @@ -0,0 +1,15 @@ +This folder (`locale`) contains translations for [TechDraw workbench templates](https://wiki.freecad.org/TechDraw_Templates) in the parent `Templates` folder. +The name of each `locale` subfolder represents a language, which follows [IETF BCP 47 standardized codes](https://en.wikipedia.org/wiki/IETF_language_tag). The original TechDraw templates in the parent folder are written using American English (`en-US`). + +As such, the most basic name for a locale subfolder will include an [ISO 639 language code](https://en.wikipedia.org/wiki/ISO_639) (e.g. `de` for German). If it's necessary, additional subtags can be added to describe language variants. For instance variants spoken in a particular country, or a specific script. Those subtags are combinable and are based in other standards. + +The most common additional subtag is an additional country code to describe a regional variant of the language (e.g. `de-DE` for German spoken in Germany, `es-AR` for Spanish spoken in Argentina, or `zh-CN` for Simplified Chinese in Mainland China). Country subtags are based on [the ISO 3166-1 standard's country codes](https://en.wikipedia.org/wiki/ISO_3166-1). + +To add a translation: + +1. Add a folder named `ll` or `ll-CC` (where `ll` is a 2-letter or 3-letter ISO 639 language code, and `CC` is a 2-letter ISO 3166-1 country code). +2. Copy over the TechDraw templates in the parent `Templates` folder that you want to translate to your new folder. +3. [Translate away!](https://wiki.freecad.org/TechDraw_Templates) +4. [Submit a PR (GitHub Pull Request)](https://freecad.github.io/DevelopersHandbook/gettingstarted/#submitting-a-pr) to get your Tech Draw template translations included in FreeCAD. + +Use the [FreeCAD forum](https://forum.freecad.org/) if you need further help. \ No newline at end of file diff --git a/src/Mod/TechDraw/Templates/locale/DE/A0_Landscape_ISO7200_DE.svg b/src/Mod/TechDraw/Templates/locale/de/A0_Landscape_ISO7200_DE.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/DE/A0_Landscape_ISO7200_DE.svg rename to src/Mod/TechDraw/Templates/locale/de/A0_Landscape_ISO7200_DE.svg diff --git a/src/Mod/TechDraw/Templates/locale/DE/A1_Landscape_ISO7200_DE.svg b/src/Mod/TechDraw/Templates/locale/de/A1_Landscape_ISO7200_DE.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/DE/A1_Landscape_ISO7200_DE.svg rename to src/Mod/TechDraw/Templates/locale/de/A1_Landscape_ISO7200_DE.svg diff --git a/src/Mod/TechDraw/Templates/locale/DE/A2_Landscape_ISO7200_DE.svg b/src/Mod/TechDraw/Templates/locale/de/A2_Landscape_ISO7200_DE.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/DE/A2_Landscape_ISO7200_DE.svg rename to src/Mod/TechDraw/Templates/locale/de/A2_Landscape_ISO7200_DE.svg diff --git a/src/Mod/TechDraw/Templates/locale/DE/A3_Landscape_ISO7200_DE.svg b/src/Mod/TechDraw/Templates/locale/de/A3_Landscape_ISO7200_DE.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/DE/A3_Landscape_ISO7200_DE.svg rename to src/Mod/TechDraw/Templates/locale/de/A3_Landscape_ISO7200_DE.svg diff --git a/src/Mod/TechDraw/Templates/locale/DE/A4_Landscape_ISO7200_DE.svg b/src/Mod/TechDraw/Templates/locale/de/A4_Landscape_ISO7200_DE.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/DE/A4_Landscape_ISO7200_DE.svg rename to src/Mod/TechDraw/Templates/locale/de/A4_Landscape_ISO7200_DE.svg diff --git a/src/Mod/TechDraw/Templates/locale/DE/A4_Portrait_ISO7200_DE.svg b/src/Mod/TechDraw/Templates/locale/de/A4_Portrait_ISO7200_DE.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/DE/A4_Portrait_ISO7200_DE.svg rename to src/Mod/TechDraw/Templates/locale/de/A4_Portrait_ISO7200_DE.svg diff --git a/src/Mod/TechDraw/Templates/locale/AR_IRAM/A0.svg b/src/Mod/TechDraw/Templates/locale/es-AR/A0.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/AR_IRAM/A0.svg rename to src/Mod/TechDraw/Templates/locale/es-AR/A0.svg diff --git a/src/Mod/TechDraw/Templates/locale/AR_IRAM/A1.svg b/src/Mod/TechDraw/Templates/locale/es-AR/A1.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/AR_IRAM/A1.svg rename to src/Mod/TechDraw/Templates/locale/es-AR/A1.svg diff --git a/src/Mod/TechDraw/Templates/locale/AR_IRAM/A2.svg b/src/Mod/TechDraw/Templates/locale/es-AR/A2.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/AR_IRAM/A2.svg rename to src/Mod/TechDraw/Templates/locale/es-AR/A2.svg diff --git a/src/Mod/TechDraw/Templates/locale/AR_IRAM/A3.svg b/src/Mod/TechDraw/Templates/locale/es-AR/A3.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/AR_IRAM/A3.svg rename to src/Mod/TechDraw/Templates/locale/es-AR/A3.svg diff --git a/src/Mod/TechDraw/Templates/locale/AR_IRAM/A4.svg b/src/Mod/TechDraw/Templates/locale/es-AR/A4.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/AR_IRAM/A4.svg rename to src/Mod/TechDraw/Templates/locale/es-AR/A4.svg diff --git a/src/Mod/TechDraw/Templates/locale/AR_IRAM/LEEME b/src/Mod/TechDraw/Templates/locale/es-AR/LEEME similarity index 100% rename from src/Mod/TechDraw/Templates/locale/AR_IRAM/LEEME rename to src/Mod/TechDraw/Templates/locale/es-AR/LEEME diff --git a/src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Landscape_A0.svg b/src/Mod/TechDraw/Templates/locale/ru/Leading/Landscape_A0.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Landscape_A0.svg rename to src/Mod/TechDraw/Templates/locale/ru/Leading/Landscape_A0.svg diff --git a/src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Landscape_A1.svg b/src/Mod/TechDraw/Templates/locale/ru/Leading/Landscape_A1.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Landscape_A1.svg rename to src/Mod/TechDraw/Templates/locale/ru/Leading/Landscape_A1.svg diff --git a/src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Landscape_A2.svg b/src/Mod/TechDraw/Templates/locale/ru/Leading/Landscape_A2.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Landscape_A2.svg rename to src/Mod/TechDraw/Templates/locale/ru/Leading/Landscape_A2.svg diff --git a/src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Landscape_A3.svg b/src/Mod/TechDraw/Templates/locale/ru/Leading/Landscape_A3.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Landscape_A3.svg rename to src/Mod/TechDraw/Templates/locale/ru/Leading/Landscape_A3.svg diff --git a/src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Landscape_A4_NotInGOST.svg b/src/Mod/TechDraw/Templates/locale/ru/Leading/Landscape_A4_NotInGOST.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Landscape_A4_NotInGOST.svg rename to src/Mod/TechDraw/Templates/locale/ru/Leading/Landscape_A4_NotInGOST.svg diff --git a/src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Portrait_A0.svg b/src/Mod/TechDraw/Templates/locale/ru/Leading/Portrait_A0.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Portrait_A0.svg rename to src/Mod/TechDraw/Templates/locale/ru/Leading/Portrait_A0.svg diff --git a/src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Portrait_A1.svg b/src/Mod/TechDraw/Templates/locale/ru/Leading/Portrait_A1.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Portrait_A1.svg rename to src/Mod/TechDraw/Templates/locale/ru/Leading/Portrait_A1.svg diff --git a/src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Portrait_A2.svg b/src/Mod/TechDraw/Templates/locale/ru/Leading/Portrait_A2.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Portrait_A2.svg rename to src/Mod/TechDraw/Templates/locale/ru/Leading/Portrait_A2.svg diff --git a/src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Portrait_A3.svg b/src/Mod/TechDraw/Templates/locale/ru/Leading/Portrait_A3.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Portrait_A3.svg rename to src/Mod/TechDraw/Templates/locale/ru/Leading/Portrait_A3.svg diff --git a/src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Portrait_A4.svg b/src/Mod/TechDraw/Templates/locale/ru/Leading/Portrait_A4.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/RU_GOST/Leading/Portrait_A4.svg rename to src/Mod/TechDraw/Templates/locale/ru/Leading/Portrait_A4.svg diff --git a/src/Mod/TechDraw/Templates/locale/RU_GOST/Leading_text/Portrait_A3.svg b/src/Mod/TechDraw/Templates/locale/ru/Leading_text/Portrait_A3.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/RU_GOST/Leading_text/Portrait_A3.svg rename to src/Mod/TechDraw/Templates/locale/ru/Leading_text/Portrait_A3.svg diff --git a/src/Mod/TechDraw/Templates/locale/RU_GOST/Leading_text/Portrait_A4.svg b/src/Mod/TechDraw/Templates/locale/ru/Leading_text/Portrait_A4.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/RU_GOST/Leading_text/Portrait_A4.svg rename to src/Mod/TechDraw/Templates/locale/ru/Leading_text/Portrait_A4.svg diff --git a/src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Landscape_A0.svg b/src/Mod/TechDraw/Templates/locale/ru/Subsequent/Landscape_A0.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Landscape_A0.svg rename to src/Mod/TechDraw/Templates/locale/ru/Subsequent/Landscape_A0.svg diff --git a/src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Landscape_A1.svg b/src/Mod/TechDraw/Templates/locale/ru/Subsequent/Landscape_A1.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Landscape_A1.svg rename to src/Mod/TechDraw/Templates/locale/ru/Subsequent/Landscape_A1.svg diff --git a/src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Landscape_A2.svg b/src/Mod/TechDraw/Templates/locale/ru/Subsequent/Landscape_A2.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Landscape_A2.svg rename to src/Mod/TechDraw/Templates/locale/ru/Subsequent/Landscape_A2.svg diff --git a/src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Landscape_A3.svg b/src/Mod/TechDraw/Templates/locale/ru/Subsequent/Landscape_A3.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Landscape_A3.svg rename to src/Mod/TechDraw/Templates/locale/ru/Subsequent/Landscape_A3.svg diff --git a/src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Landscape_A4.svg b/src/Mod/TechDraw/Templates/locale/ru/Subsequent/Landscape_A4.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Landscape_A4.svg rename to src/Mod/TechDraw/Templates/locale/ru/Subsequent/Landscape_A4.svg diff --git a/src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Portrait_A0.svg b/src/Mod/TechDraw/Templates/locale/ru/Subsequent/Portrait_A0.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Portrait_A0.svg rename to src/Mod/TechDraw/Templates/locale/ru/Subsequent/Portrait_A0.svg diff --git a/src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Portrait_A1.svg b/src/Mod/TechDraw/Templates/locale/ru/Subsequent/Portrait_A1.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Portrait_A1.svg rename to src/Mod/TechDraw/Templates/locale/ru/Subsequent/Portrait_A1.svg diff --git a/src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Portrait_A2.svg b/src/Mod/TechDraw/Templates/locale/ru/Subsequent/Portrait_A2.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Portrait_A2.svg rename to src/Mod/TechDraw/Templates/locale/ru/Subsequent/Portrait_A2.svg diff --git a/src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Portrait_A3.svg b/src/Mod/TechDraw/Templates/locale/ru/Subsequent/Portrait_A3.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Portrait_A3.svg rename to src/Mod/TechDraw/Templates/locale/ru/Subsequent/Portrait_A3.svg diff --git a/src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Portrait_A4.svg b/src/Mod/TechDraw/Templates/locale/ru/Subsequent/Portrait_A4.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/RU_GOST/Subsequent/Portrait_A4.svg rename to src/Mod/TechDraw/Templates/locale/ru/Subsequent/Portrait_A4.svg diff --git a/src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A0_Landscape_CN_CLIP.svg b/src/Mod/TechDraw/Templates/locale/zh-CN/landscape/A0_Landscape_CN_CLIP.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A0_Landscape_CN_CLIP.svg rename to src/Mod/TechDraw/Templates/locale/zh-CN/landscape/A0_Landscape_CN_CLIP.svg diff --git a/src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A0_Landscape_CN_NO_CLIP.svg b/src/Mod/TechDraw/Templates/locale/zh-CN/landscape/A0_Landscape_CN_NO_CLIP.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A0_Landscape_CN_NO_CLIP.svg rename to src/Mod/TechDraw/Templates/locale/zh-CN/landscape/A0_Landscape_CN_NO_CLIP.svg diff --git a/src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A1_Landscape_CN_CLIP.svg b/src/Mod/TechDraw/Templates/locale/zh-CN/landscape/A1_Landscape_CN_CLIP.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A1_Landscape_CN_CLIP.svg rename to src/Mod/TechDraw/Templates/locale/zh-CN/landscape/A1_Landscape_CN_CLIP.svg diff --git a/src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A1_Landscape_CN_NO_CLIP.svg b/src/Mod/TechDraw/Templates/locale/zh-CN/landscape/A1_Landscape_CN_NO_CLIP.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A1_Landscape_CN_NO_CLIP.svg rename to src/Mod/TechDraw/Templates/locale/zh-CN/landscape/A1_Landscape_CN_NO_CLIP.svg diff --git a/src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A2_Landscape_CN_CLIP.svg b/src/Mod/TechDraw/Templates/locale/zh-CN/landscape/A2_Landscape_CN_CLIP.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A2_Landscape_CN_CLIP.svg rename to src/Mod/TechDraw/Templates/locale/zh-CN/landscape/A2_Landscape_CN_CLIP.svg diff --git a/src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A2_Landscape_CN_NO_CLIP.svg b/src/Mod/TechDraw/Templates/locale/zh-CN/landscape/A2_Landscape_CN_NO_CLIP.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A2_Landscape_CN_NO_CLIP.svg rename to src/Mod/TechDraw/Templates/locale/zh-CN/landscape/A2_Landscape_CN_NO_CLIP.svg diff --git a/src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A3_Landscape_CN_CLIP.svg b/src/Mod/TechDraw/Templates/locale/zh-CN/landscape/A3_Landscape_CN_CLIP.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A3_Landscape_CN_CLIP.svg rename to src/Mod/TechDraw/Templates/locale/zh-CN/landscape/A3_Landscape_CN_CLIP.svg diff --git a/src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A3_Landscape_CN_NO_CLIP.svg b/src/Mod/TechDraw/Templates/locale/zh-CN/landscape/A3_Landscape_CN_NO_CLIP.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A3_Landscape_CN_NO_CLIP.svg rename to src/Mod/TechDraw/Templates/locale/zh-CN/landscape/A3_Landscape_CN_NO_CLIP.svg diff --git a/src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A4_Landscape_CN_CLIP.svg b/src/Mod/TechDraw/Templates/locale/zh-CN/landscape/A4_Landscape_CN_CLIP.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A4_Landscape_CN_CLIP.svg rename to src/Mod/TechDraw/Templates/locale/zh-CN/landscape/A4_Landscape_CN_CLIP.svg diff --git a/src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A4_Landscape_CN_NO_CLIP.svg b/src/Mod/TechDraw/Templates/locale/zh-CN/landscape/A4_Landscape_CN_NO_CLIP.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/zh_CN/landscape/A4_Landscape_CN_NO_CLIP.svg rename to src/Mod/TechDraw/Templates/locale/zh-CN/landscape/A4_Landscape_CN_NO_CLIP.svg diff --git a/src/Mod/TechDraw/Templates/locale/zh_CN/landscape/README b/src/Mod/TechDraw/Templates/locale/zh-CN/landscape/README similarity index 100% rename from src/Mod/TechDraw/Templates/locale/zh_CN/landscape/README rename to src/Mod/TechDraw/Templates/locale/zh-CN/landscape/README diff --git a/src/Mod/TechDraw/Templates/locale/zh_CN/portrait/A3_Portrait_CN_CLIP.svg b/src/Mod/TechDraw/Templates/locale/zh-CN/portrait/A3_Portrait_CN_CLIP.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/zh_CN/portrait/A3_Portrait_CN_CLIP.svg rename to src/Mod/TechDraw/Templates/locale/zh-CN/portrait/A3_Portrait_CN_CLIP.svg diff --git a/src/Mod/TechDraw/Templates/locale/zh_CN/portrait/A4_Portrait_CN_CLIP.svg b/src/Mod/TechDraw/Templates/locale/zh-CN/portrait/A4_Portrait_CN_CLIP.svg similarity index 100% rename from src/Mod/TechDraw/Templates/locale/zh_CN/portrait/A4_Portrait_CN_CLIP.svg rename to src/Mod/TechDraw/Templates/locale/zh-CN/portrait/A4_Portrait_CN_CLIP.svg From a88e7baff5b012c861085016a9165fbf3556648f Mon Sep 17 00:00:00 2001 From: bgbsww Date: Sat, 13 Apr 2024 18:26:36 -0400 Subject: [PATCH 22/24] TopoShape/Part: Bug fixes; pass Hasher parms, OCCT 7.8 compatibility, ElementMaps correct item count --- src/App/ElementMap.cpp | 4 ++-- src/Mod/Part/App/ExtrusionHelper.cpp | 11 ++++++----- src/Mod/Part/App/ExtrusionHelper.h | 7 +++++-- src/Mod/Part/App/FaceMaker.cpp | 19 +++++++++++++++++++ src/Mod/Part/App/FaceMaker.h | 2 ++ src/Mod/Part/App/FeatureExtrusion.cpp | 2 +- src/Mod/Part/App/FeatureOffset.cpp | 3 ++- src/Mod/Part/App/PartFeatures.cpp | 3 ++- src/Mod/Part/App/TopoShapeExpansion.cpp | 4 +--- src/Mod/Part/App/TopoShapePyImp.cpp | 4 ++++ src/Mod/PartDesign/App/FeatureExtrude.cpp | 6 +++--- .../TestTopologicalNamingProblem.py | 14 +++++++++----- src/Mod/Sketcher/App/SketchObject.cpp | 2 +- 13 files changed, 57 insertions(+), 24 deletions(-) diff --git a/src/App/ElementMap.cpp b/src/App/ElementMap.cpp index d6012c7215..4a2545e388 100644 --- a/src/App/ElementMap.cpp +++ b/src/App/ElementMap.cpp @@ -726,8 +726,8 @@ MappedName ElementMap::dehashElementName(const MappedName& name) const FC_LOG("cannot de-hash id " << id);// NOLINT return name; } - MappedName ret( - sid.toString());// FIXME .toString() was missing in original function. is this correct? + MappedName ret(sid); +// sid.toString());// FIXME .toString() was missing in original function. is this correct? FC_TRACE("de-hash " << name << " -> " << ret);// NOLINT return ret; } diff --git a/src/Mod/Part/App/ExtrusionHelper.cpp b/src/Mod/Part/App/ExtrusionHelper.cpp index 191d260dc5..bc99d7ccd4 100644 --- a/src/Mod/Part/App/ExtrusionHelper.cpp +++ b/src/Mod/Part/App/ExtrusionHelper.cpp @@ -484,7 +484,8 @@ void ExtrusionHelper::createTaperedPrismOffset(TopoDS_Wire sourceWire, void ExtrusionHelper::makeElementDraft(const ExtrusionParameters& params, const TopoShape& _shape, - std::vector& drafts) + std::vector& drafts, + App::StringHasherRef hasher) { double distanceFwd = tan(params.taperAngleFwd) * params.lengthFwd; double distanceRev = tan(params.taperAngleRev) * params.lengthRev; @@ -518,7 +519,7 @@ void ExtrusionHelper::makeElementDraft(const ExtrusionParameters& params, } else { unsigned pos = drafts.size(); - makeElementDraft(params, outerWire, drafts); + makeElementDraft(params, outerWire, drafts, hasher); if (drafts.size() != pos + 1) { Standard_Failure::Raise("Failed to make drafted extrusion"); } @@ -528,7 +529,7 @@ void ExtrusionHelper::makeElementDraft(const ExtrusionParameters& params, wires, "", TopoShape::SingleShapeCompoundCreationPolicy::returnShape); - makeElementDraft(params, innerWires, inner); + makeElementDraft(params, innerWires, inner, hasher); if (inner.empty()) { Standard_Failure::Raise("Failed to make drafted extrusion with inner hole"); } @@ -550,7 +551,7 @@ void ExtrusionHelper::makeElementDraft(const ExtrusionParameters& params, } else if (shape.shapeType() == TopAbs_COMPOUND) { for (auto& s : shape.getSubTopoShapes()) { - makeElementDraft(params, s, drafts); + makeElementDraft(params, s, drafts, hasher); } } else { @@ -598,7 +599,7 @@ void ExtrusionHelper::makeElementDraft(const ExtrusionParameters& params, } mkGenerator.Build(); - drafts.push_back(TopoShape(0).makeElementShape(mkGenerator, list_of_sections)); + drafts.push_back(TopoShape(0, hasher).makeElementShape(mkGenerator, list_of_sections)); } catch (Standard_Failure&) { throw; diff --git a/src/Mod/Part/App/ExtrusionHelper.h b/src/Mod/Part/App/ExtrusionHelper.h index bff010a94d..0fc61d1699 100644 --- a/src/Mod/Part/App/ExtrusionHelper.h +++ b/src/Mod/Part/App/ExtrusionHelper.h @@ -29,6 +29,7 @@ #include #include +#include "TopoShape.h" namespace Part @@ -88,8 +89,10 @@ public: TopoDS_Wire& result); /** Same as makeDraft() with support of element mapping */ - static void - makeElementDraft(const ExtrusionParameters& params, const TopoShape&, std::vector&); + static void makeElementDraft(const ExtrusionParameters& params, + const TopoShape&, + std::vector&, + App::StringHasherRef hasher); }; } //namespace Part diff --git a/src/Mod/Part/App/FaceMaker.cpp b/src/Mod/Part/App/FaceMaker.cpp index c0216a44f7..5f4bc2aa5d 100644 --- a/src/Mod/Part/App/FaceMaker.cpp +++ b/src/Mod/Part/App/FaceMaker.cpp @@ -61,9 +61,12 @@ void Part::FaceMaker::addTopoShape(const TopoShape& shape) { break; case TopAbs_WIRE: this->myWires.push_back(TopoDS::Wire(sh)); + this->myTopoWires.push_back(shape); break; case TopAbs_EDGE: this->myWires.push_back(BRepBuilderAPI_MakeWire(TopoDS::Edge(sh)).Wire()); + this->myTopoWires.push_back(shape); + this->myTopoWires.back().setShape(this->myWires.back(), false); break; case TopAbs_FACE: this->myInputFaces.push_back(sh); @@ -186,6 +189,7 @@ void Part::FaceMaker::postBuild() { if(!op) op = Part::OpCodes::Face; const auto &faces = this->myTopoShape.getSubTopoShapes(TopAbs_FACE); + std::set namesUsed; // name the face using the edges of its outer wire for(auto &face : faces) { ++index; @@ -208,10 +212,25 @@ void Part::FaceMaker::postBuild() { std::vector names; Data::ElementIDRefs sids; +#ifdef FC_USE_TNP_FIX + // To avoid name collision, we keep track of any used names to make sure + // to use at least 'minElementNames' number of unused element names to + // generate the face name. + int nameCount = 0; + for (const auto &e : edgeNames) { + names.push_back(e.name); + sids += e.sids; + if (namesUsed.insert(e.name).second) { + if (++nameCount >= minElementNames) + break; + } + } +#else // We just use the first source element name to make the face name more // stable names.push_back(edgeNames.begin()->name); sids = edgeNames.begin()->sids; +#endif this->myTopoShape.setElementComboName( Data::IndexedName::fromConst("Face",index),names,op,nullptr,&sids); } diff --git a/src/Mod/Part/App/FaceMaker.h b/src/Mod/Part/App/FaceMaker.h index 78e0553a8b..560b1a8509 100644 --- a/src/Mod/Part/App/FaceMaker.h +++ b/src/Mod/Part/App/FaceMaker.h @@ -107,10 +107,12 @@ public: protected: std::vector mySourceShapes; //wire or compound std::vector myWires; //wires from mySourceShapes + std::vector myTopoWires; std::vector myCompounds; //compounds, for recursive processing std::vector myShapesToReturn; std::vector myInputFaces; TopoShape myTopoShape; + int minElementNames = 1; /** * @brief Build_Essence: build routine that can assume there is no nesting. diff --git a/src/Mod/Part/App/FeatureExtrusion.cpp b/src/Mod/Part/App/FeatureExtrusion.cpp index ae12b30f2c..7c7822a6ec 100644 --- a/src/Mod/Part/App/FeatureExtrusion.cpp +++ b/src/Mod/Part/App/FeatureExtrusion.cpp @@ -320,7 +320,7 @@ void Extrusion::extrudeShape(TopoShape &result, const TopoShape &source, const E Base::SignalException se; #endif std::vector drafts; - ExtrusionHelper::makeElementDraft(params, myShape, drafts); + ExtrusionHelper::makeElementDraft(params, myShape, drafts, result.Hasher); if (drafts.empty()) { Standard_Failure::Raise("Drafting shape failed"); } diff --git a/src/Mod/Part/App/FeatureOffset.cpp b/src/Mod/Part/App/FeatureOffset.cpp index 103b44b232..6ac5bed437 100644 --- a/src/Mod/Part/App/FeatureOffset.cpp +++ b/src/Mod/Part/App/FeatureOffset.cpp @@ -28,6 +28,7 @@ #include #include "FeatureOffset.h" +#include using namespace Part; @@ -96,7 +97,7 @@ App::DocumentObjectExecReturn *Offset::execute() if(shape.isNull()) return new App::DocumentObjectExecReturn("Invalid source link"); auto join = static_cast(Join.getValue()); - this->Shape.setValue(TopoShape(0).makeElementOffset( + this->Shape.setValue(TopoShape(0, getDocument()->getStringHasher()).makeElementOffset( shape,offset,tol,inter,self,mode,join,fill ? FillType::fill : FillType::noFill)); #endif return App::DocumentObject::StdReturn; diff --git a/src/Mod/Part/App/PartFeatures.cpp b/src/Mod/Part/App/PartFeatures.cpp index e532cef891..6992986bf8 100644 --- a/src/Mod/Part/App/PartFeatures.cpp +++ b/src/Mod/Part/App/PartFeatures.cpp @@ -45,6 +45,7 @@ #include +#include #include "PartFeatures.h" #include "TopoShapeOpCode.h" @@ -837,7 +838,7 @@ App::DocumentObjectExecReturn* Thickness::execute() short join = (short)Join.getValue(); #ifdef FC_USE_TNP_FIX - this->Shape.setValue(TopoShape(0) + this->Shape.setValue(TopoShape(0,getDocument()->getStringHasher()) .makeElementThickSolid(base, shapes, thickness, diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index d8bcd46ab3..509ae02920 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -1435,7 +1435,6 @@ TopoShape& TopoShape::makeShapeWithElementMap(const TopoDS_Shape& shape, if (otherMap.count() == 0) { continue; } - for (int i = 1; i <= otherMap.count(); i++) { const auto& otherElement = otherMap.find(incomingShape._Shape, i); // Find all new objects that are a modification of the old object @@ -1754,7 +1753,6 @@ TopoShape& TopoShape::makeShapeWithElementMap(const TopoDS_Shape& shape, elementMap() ->encodeElementName(element[0], first_name, ss, &sids, Tag, op, first_key.tag); elementMap()->setElementName(element, first_name, Tag, &sids); - if (!delayed && first_key.shapetype < 3) { newNames.erase(itName); } @@ -1852,7 +1850,7 @@ TopoShape& TopoShape::makeShapeWithElementMap(const TopoDS_Shape& shape, elementMap()->encodeElementName(indexedName[0], newName, ss, &sids, Tag, op); elementMap()->setElementName(indexedName, newName, Tag, &sids); - } + } } } diff --git a/src/Mod/Part/App/TopoShapePyImp.cpp b/src/Mod/Part/App/TopoShapePyImp.cpp index c7e1af537a..848c827926 100644 --- a/src/Mod/Part/App/TopoShapePyImp.cpp +++ b/src/Mod/Part/App/TopoShapePyImp.cpp @@ -122,7 +122,11 @@ static Py_hash_t _TopoShapeHash(PyObject* self) "This reference is no longer valid!"); return 0; } +#if OCC_VERSION_HEX >= 0x070800 + return std::hash {}(static_cast(self)->getTopoShapePtr()->getShape()); +#else return static_cast(self)->getTopoShapePtr()->getShape().HashCode(INT_MAX); +#endif } struct TopoShapePyInit diff --git a/src/Mod/PartDesign/App/FeatureExtrude.cpp b/src/Mod/PartDesign/App/FeatureExtrude.cpp index 4f7dd026b0..0673d960c9 100644 --- a/src/Mod/PartDesign/App/FeatureExtrude.cpp +++ b/src/Mod/PartDesign/App/FeatureExtrude.cpp @@ -555,7 +555,7 @@ App::DocumentObjectExecReturn* FeatureExtrude::buildExtrusion(ExtrudeOptions opt } sketchshape.move(invObjLoc); - TopoShape prism(0); + TopoShape prism(0, getDocument()->getStringHasher()); if (method == "UpToFirst" || method == "UpToLast" || method == "UpToFace") { // Note: This will return an unlimited planar face if support is a datum plane @@ -663,7 +663,7 @@ App::DocumentObjectExecReturn* FeatureExtrude::buildExtrusion(ExtrudeOptions opt params.dir.Reverse(); } std::vector drafts; - Part::ExtrusionHelper::makeElementDraft(params, sketchshape, drafts); + Part::ExtrusionHelper::makeElementDraft(params, sketchshape, drafts, getDocument()->getStringHasher()); if (drafts.empty()) { return new App::DocumentObjectExecReturn( QT_TRANSLATE_NOOP("Exception", "Padding with draft angle failed")); @@ -693,7 +693,7 @@ App::DocumentObjectExecReturn* FeatureExtrude::buildExtrusion(ExtrudeOptions opt prism.Tag = -this->getID(); // Let's call algorithm computing a fuse operation: - TopoShape result(0); + TopoShape result(0, getDocument()->getStringHasher()); try { const char* maker; switch (getAddSubType()) { diff --git a/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py b/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py index be2c42b789..dd0f191b76 100644 --- a/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py +++ b/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py @@ -437,17 +437,22 @@ class TestTopologicalNamingProblem(unittest.TestCase): # Act pad = self.Doc.addObject('PartDesign::Pad', 'Pad') pad.Profile = sketch - pad.BaseFeature = sketch body.addObject(sketch) body.addObject(pad) self.Doc.recompute() # Assert - self.assertEqual(len(body.Shape.childShapes()), 1) - self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 26) + # self.assertEqual(len(body.Shape.childShapes()), 1) + if App.GuiUp: + # Todo: This triggers a 'hasher mismatch' warning in TopoShape::mapSubElement as called by + # flushElementMap. This appears to be the case whenever you have a parent with a hasher + # that has children without hashmaps. The warning seems to be spurious in this case, but + # perhaps there is a solution involving setting hashmaps on all elements. + self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 30) + else: + self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 26) self.assertEqual(body.Shape.ElementMapSize,30) self.assertEqual(sketch.Shape.ElementMapSize,12) self.assertEqual(pad.Shape.ElementMapSize,30) - self.assertEqual(pad.Shape.childShapes()[0].ElementMapSize,30) # Todo: Assert that the names in the ElementMap are good; in particular that they are hashed with a # starting def testPartDesignElementMapRevolution(self): @@ -584,7 +589,6 @@ class TestTopologicalNamingProblem(unittest.TestCase): self.assertEqual(pad.Shape.ElementMapSize, 30) # The sketch plus the pad in the map # TODO: differing results between main and LS3 on these values. Does it matter? # self.assertEqual(body.Shape.ElementMapSize,0) # 8? - # self.Doc.recompute() # self.assertEqual(body.Shape.ElementMapSize,30) # 26 def testPlaneElementMap(self): diff --git a/src/Mod/Sketcher/App/SketchObject.cpp b/src/Mod/Sketcher/App/SketchObject.cpp index 4ebe85e5c3..17b3472772 100644 --- a/src/Mod/Sketcher/App/SketchObject.cpp +++ b/src/Mod/Sketcher/App/SketchObject.cpp @@ -299,7 +299,7 @@ void SketchObject::buildShape() Shape.setValue(Part::TopoShape()); return; } - Part::TopoShape result(0); + Part::TopoShape result(0, getDocument()->getStringHasher()); if (vertices.empty()) { // Notice here we supply op code Part::OpCodes::Sketch to makEWires(). result.makeElementWires(shapes,Part::OpCodes::Sketch); From 9fb2931781868cd903f95e3aadb45d9c2abd09f0 Mon Sep 17 00:00:00 2001 From: wmayer Date: Sun, 14 Apr 2024 19:08:14 +0200 Subject: [PATCH 23/24] Base: fix some linter warnings --- src/Base/Parameter.cpp | 62 ++++++++++++++++++++++++------------------ src/Base/Parameter.h | 8 +++--- 2 files changed, 40 insertions(+), 30 deletions(-) diff --git a/src/Base/Parameter.cpp b/src/Base/Parameter.cpp index 95dba60791..665a8fcf14 100644 --- a/src/Base/Parameter.cpp +++ b/src/Base/Parameter.cpp @@ -171,9 +171,7 @@ inline bool DOMTreeErrorReporter::getSawErrors() const ParameterGrp::ParameterGrp(XERCES_CPP_NAMESPACE_QUALIFIER DOMElement* GroupNode, const char* sName, ParameterGrp* Parent) - : Base::Handled() - , Subject() - , _pGroupNode(GroupNode) + : _pGroupNode(GroupNode) , _Parent(Parent) { if (sName) { @@ -202,7 +200,7 @@ ParameterGrp::~ParameterGrp() //************************************************************************** // Access methods -void ParameterGrp::copyTo(Base::Reference Grp) +void ParameterGrp::copyTo(const Base::Reference& Grp) { if (Grp == this) { return; @@ -215,7 +213,7 @@ void ParameterGrp::copyTo(Base::Reference Grp) insertTo(Grp); } -void ParameterGrp::insertTo(Base::Reference Grp) +void ParameterGrp::insertTo(const Base::Reference& Grp) { if (Grp == this) { return; @@ -309,7 +307,7 @@ void ParameterGrp::revert(const char* FileName) revert(Base::Reference(Mngr)); } -void ParameterGrp::revert(Base::Reference Grp) +void ParameterGrp::revert(const Base::Reference& Grp) { if (Grp == this) { return; @@ -869,8 +867,10 @@ unsigned long ParameterGrp::GetUnsigned(const char* Name, unsigned long lPreset) if (!pcElem) { return lPreset; } + // if yes check the value and return - return strtoul(StrX(pcElem->getAttribute(XStr("Value").unicodeForm())).c_str(), nullptr, 10); + const int base = 10; + return strtoul(StrX(pcElem->getAttribute(XStr("Value").unicodeForm())).c_str(), nullptr, base); } void ParameterGrp::SetUnsigned(const char* Name, unsigned long lValue) @@ -887,6 +887,7 @@ std::vector ParameterGrp::GetUnsigneds(const char* sFilter) const } std::string Name; + const int base = 10; DOMElement* pcTemp = FindElement(_pGroupNode, "FCUInt"); while (pcTemp) { @@ -896,7 +897,7 @@ std::vector ParameterGrp::GetUnsigneds(const char* sFilter) const vrValues.push_back( strtoul(StrX(pcTemp->getAttribute(XStr("Value").unicodeForm())).c_str(), nullptr, - 10)); + base)); } pcTemp = FindNextElement(pcTemp, "FCUInt"); } @@ -913,6 +914,7 @@ ParameterGrp::GetUnsignedMap(const char* sFilter) const } std::string Name; + const int base = 10; DOMElement* pcTemp = FindElement(_pGroupNode, "FCUInt"); while (pcTemp) { @@ -923,7 +925,7 @@ ParameterGrp::GetUnsignedMap(const char* sFilter) const Name, (strtoul(StrX(pcTemp->getAttribute(XStr("Value").unicodeForm())).c_str(), nullptr, - 10))); + base))); } pcTemp = FindNextElement(pcTemp, "FCUInt"); } @@ -1398,11 +1400,11 @@ ParameterGrp::FindElement(XERCES_CPP_NAMESPACE_QUALIFIER DOMElement* Start, if (Name) { DOMNode* attr = FindAttribute(clChild, "Name"); if (attr && !strcmp(Name, StrX(attr->getNodeValue()).c_str())) { - return static_cast(clChild); + return dynamic_cast(clChild); } } else { - return static_cast(clChild); + return dynamic_cast(clChild); } } } @@ -1423,7 +1425,7 @@ ParameterGrp::FindNextElement(XERCES_CPP_NAMESPACE_QUALIFIER DOMNode* Prev, cons if (clChild->getNodeType() == DOMNode::ELEMENT_NODE) { // the right node Type if (!strcmp(Type, StrX(clChild->getNodeName()).c_str())) { - return static_cast(clChild); + return dynamic_cast(clChild); } } } @@ -1529,8 +1531,8 @@ void ParameterGrp::_Reset() //************************************************************************** // ParameterSerializer //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -ParameterSerializer::ParameterSerializer(const std::string& fn) - : filename(fn) +ParameterSerializer::ParameterSerializer(std::string fn) + : filename(std::move(fn)) {} ParameterSerializer::~ParameterSerializer() = default; @@ -1555,7 +1557,7 @@ bool ParameterSerializer::LoadOrCreateDocument(ParameterManager& mgr) // ParameterManager //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -static XercesDOMParser::ValSchemes gValScheme = XercesDOMParser::Val_Auto; +static XercesDOMParser::ValSchemes gValScheme = XercesDOMParser::Val_Auto; // NOLINT //************************************************************************** // Construction/Destruction @@ -1610,6 +1612,7 @@ ParameterManager::ParameterManager() // // --------------------------------------------------------------------------- + // NOLINTBEGIN gDoNamespaces = false; gDoSchema = false; gSchemaFullChecking = false; @@ -1622,6 +1625,7 @@ ParameterManager::ParameterManager() gDiscardDefaultContent = true; gUseFilter = true; gFormatPrettyPrint = true; + // NOLINTEND } /** Destruction @@ -1736,8 +1740,9 @@ int ParameterManager::LoadDocument(const char* sFileName) try { #if defined(FC_OS_WIN32) - LocalFileInputSource inputSource( - reinterpret_cast(file.toStdWString().c_str())); + std::wstring name = file.toStdWString(); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + LocalFileInputSource inputSource(reinterpret_cast(name.c_str())); #else LocalFileInputSource inputSource(XStr(file.filePath().c_str()).unicodeForm()); #endif @@ -1760,14 +1765,14 @@ int ParameterManager::LoadDocument(const XERCES_CPP_NAMESPACE_QUALIFIER InputSou // The parser will call back to methods of the ErrorHandler if it // discovers errors during the course of parsing the XML document. // - XercesDOMParser* parser = new XercesDOMParser; + auto parser = new XercesDOMParser; parser->setValidationScheme(gValScheme); parser->setDoNamespaces(gDoNamespaces); parser->setDoSchema(gDoSchema); parser->setValidationSchemaFullChecking(gSchemaFullChecking); parser->setCreateEntityReferenceNodes(gDoCreate); - DOMTreeErrorReporter* errReporter = new DOMTreeErrorReporter(); + auto errReporter = new DOMTreeErrorReporter(); parser->setErrorHandler(errReporter); // @@ -1778,19 +1783,16 @@ int ParameterManager::LoadDocument(const XERCES_CPP_NAMESPACE_QUALIFIER InputSou try { parser->parse(inputSource); } - catch (const XMLException& e) { std::cerr << "An error occurred during parsing\n Message: " << StrX(e.getMessage()) << std::endl; errorsOccured = true; } - catch (const DOMException& e) { std::cerr << "A DOM error occurred during parsing\n DOMException code: " << e.code << std::endl; errorsOccured = true; } - catch (...) { std::cerr << "An error occurred during parsing\n " << std::endl; errorsOccured = true; @@ -1836,11 +1838,13 @@ void ParameterManager::SaveDocument(const char* sFileName) const // LocalFileFormatTarget prints the resultant XML stream // to a file once it receives any thing from the serializer. // + XMLFormatTarget* myFormTarget {}; #if defined(FC_OS_WIN32) - XMLFormatTarget* myFormTarget = - new LocalFileFormatTarget(reinterpret_cast(file.toStdWString().c_str())); + std::wstring name = file.toStdWString(); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + myFormTarget = new LocalFileFormatTarget(reinterpret_cast(name.c_str())); #else - XMLFormatTarget* myFormTarget = new LocalFileFormatTarget(file.filePath().c_str()); + myFormTarget = new LocalFileFormatTarget(file.filePath().c_str()); #endif SaveDocument(myFormTarget); delete myFormTarget; @@ -1857,12 +1861,14 @@ void ParameterManager::SaveDocument(XMLFormatTarget* pFormatTarget) const std::unique_ptr myFilter; std::unique_ptr myErrorHandler; + // NOLINTBEGIN // get a serializer, an instance of DOMWriter XMLCh tempStr[100]; XMLString::transcode("LS", tempStr, 99); DOMImplementation* impl = DOMImplementationRegistry::getDOMImplementation(tempStr); DOMLSSerializer* theSerializer = static_cast(impl)->createLSSerializer(); + // NOLINTEND // set user specified end of line sequence and output encoding theSerializer->setNewLine(gMyEOLSequence); @@ -1885,6 +1891,8 @@ void ParameterManager::SaveDocument(XMLFormatTarget* pFormatTarget) const // plug in user's own error handler myErrorHandler = std::make_unique(); DOMConfiguration* config = theSerializer->getDomConfig(); + + // NOLINTBEGIN config->setParameter(XMLUni::fgDOMErrorHandler, myErrorHandler.get()); // set feature if the serializer supports the feature/mode @@ -1900,6 +1908,7 @@ void ParameterManager::SaveDocument(XMLFormatTarget* pFormatTarget) const if (config->canSetParameter(XMLUni::fgDOMWRTFormatPrettyPrint, gFormatPrettyPrint)) { config->setParameter(XMLUni::fgDOMWRTFormatPrettyPrint, gFormatPrettyPrint); } + // NOLINTEND theOutput->setByteStream(pFormatTarget); theSerializer->write(_pDocument, theOutput); @@ -1955,7 +1964,8 @@ void ParameterManager::CheckDocument() const // Either load the XSD file from disk or use the built-in string // const char* xsdFile = "..."; - std::string xsdStr(xmlSchemeString); + std::string xsdStr(xmlSchemeString); // NOLINT + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) MemBufInputSource xsdFile(reinterpret_cast(xsdStr.c_str()), xsdStr.size(), "Parameter.xsd"); diff --git a/src/Base/Parameter.h b/src/Base/Parameter.h index 374a4d0980..a3bc1f3848 100644 --- a/src/Base/Parameter.h +++ b/src/Base/Parameter.h @@ -102,9 +102,9 @@ public: /** @name copy and insertation */ //@{ /// make a deep copy to the other group - void copyTo(Base::Reference); + void copyTo(const Base::Reference&); /// overwrite everything similar, leave the others alone - void insertTo(Base::Reference); + void insertTo(const Base::Reference&); /// export this group to a file void exportTo(const char* FileName); /// import from a file to this group @@ -114,7 +114,7 @@ public: /// revert to default value by deleting any parameter that has the same value in the given file void revert(const char* FileName); /// revert to default value by deleting any parameter that has the same value in the given group - void revert(Base::Reference); + void revert(const Base::Reference&); //@} /** @name methods for group handling */ @@ -351,7 +351,7 @@ protected: class BaseExport ParameterSerializer { public: - explicit ParameterSerializer(const std::string& fn); + explicit ParameterSerializer(std::string fn); ParameterSerializer(const ParameterSerializer&) = delete; ParameterSerializer(ParameterSerializer&&) = delete; virtual ~ParameterSerializer(); From e05e17c9545d351ed9450821b7ac6167da4020cf Mon Sep 17 00:00:00 2001 From: wmayer Date: Sun, 14 Apr 2024 16:06:54 +0200 Subject: [PATCH 24/24] PD: Fixes #13446: Crash with hole in additive loft --- src/Mod/PartDesign/App/FeatureHole.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/PartDesign/App/FeatureHole.cpp b/src/Mod/PartDesign/App/FeatureHole.cpp index 2fa37d1018..cc16eb0ac4 100644 --- a/src/Mod/PartDesign/App/FeatureHole.cpp +++ b/src/Mod/PartDesign/App/FeatureHole.cpp @@ -2000,7 +2000,7 @@ TopoDS_Compound Hole::findHoles(const TopoDS_Shape& profileshape, Handle(Geom_Curve) c = BRep_Tool::Curve(edge, c_start, c_end); // Circle? - if (c->DynamicType() != STANDARD_TYPE(Geom_Circle)) { + if (c.IsNull() || c->DynamicType() != STANDARD_TYPE(Geom_Circle)) { continue; }