From b241c0d4e69f5fd00a687de810546e3401f1fd49 Mon Sep 17 00:00:00 2001 From: bgbsww Date: Wed, 20 Mar 2024 17:06:30 -0400 Subject: [PATCH] Toponaming/Part: Rework and clean python interface and add tests --- src/Mod/Part/App/AppPartPy.cpp | 472 +++++++++++++++++------- src/Mod/Part/App/FeaturePartBox.cpp | 2 +- src/Mod/Part/App/TopoShapePyImp.cpp | 18 +- src/Mod/Part/PartEnums.py | 4 + src/Mod/Part/TestPartApp.py | 2 +- src/Mod/Part/parttests/TopoShapeTest.py | 110 ++++-- 6 files changed, 433 insertions(+), 175 deletions(-) diff --git a/src/Mod/Part/App/AppPartPy.cpp b/src/Mod/Part/App/AppPartPy.cpp index 6d223f886f..83246b9ecc 100644 --- a/src/Mod/Part/App/AppPartPy.cpp +++ b/src/Mod/Part/App/AppPartPy.cpp @@ -103,11 +103,11 @@ #include "TopoShapeShellPy.h" #include "TopoShapeSolidPy.h" #include "TopoShapeWirePy.h" +#include "TopoShapeOpCode.h" +#include "TopoShapeMapper.h" #ifdef FCUseFreeType # include "FT2FC.h" -#include "TopoShapeOpCode.h" -#include "TopoShapeMapper.h" #endif extern const char* BRepBuilderAPI_FaceErrorText(BRepBuilderAPI_FaceError fe); @@ -423,7 +423,8 @@ public: add_varargs_method("getFacets",&Module::getFacets, "getFacets(shape): simplified mesh generation" ); - add_varargs_method("makeCompound",&Module::makeCompound, +#ifdef FC_USE_TNP_FIX + add_keyword_method("makeCompound",&Module::makeCompound, "makeCompound(list) -- Create a compound out of a list of shapes." ); add_keyword_method("makeShell",&Module::makeShell, @@ -442,6 +443,27 @@ public: add_keyword_method("makeSolid",&Module::makeSolid, "makeSolid(shape): Create a solid out of shells of shape. If shape is a compsolid, the overall volume solid is created." ); +#else + add_varargs_method("makeCompound",&Module::makeCompound, + "makeCompound(list) -- Create a compound out of a list of shapes." + ); + add_varargs_method("makeShell",&Module::makeShell, + "makeShell(list) -- Create a shell out of a list of faces." + ); + add_varargs_method("makeFace",&Module::makeFace, + "makeFace(list_of_shapes_or_compound, maker_class_name) -- Create a face (faces) using facemaker class.\n" + "maker_class_name is a string like 'Part::FaceMakerSimple'." + ); + add_varargs_method("makeFilledSurface",&Module::makeFilledSurface, + "makeFilledSurface(list of curves, tolerance) -- Create a surface out of a list of curves." + ); + add_varargs_method("makeFilledFace",&Module::makeFilledFace, + "makeFilledFace(list) -- Create a face out of a list of edges." + ); + add_varargs_method("makeSolid",&Module::makeSolid, + "makeSolid(shape): Create a solid out of shells of shape. If shape is a compsolid, the overall volume solid is created." + ); +#endif add_varargs_method("makePlane",&Module::makePlane, "makePlane(length,width,[pnt,dirZ,dirX]) -- Make a plane\n" "By default pnt=Vector(0,0,0) and dirZ=Vector(0,0,1), dirX is ignored in this case" @@ -522,6 +544,7 @@ public: "By default vmin/vmax=bounds of the curve, angle=360, pnt=Vector(0,0,0),\n" "dir=Vector(0,0,1) and shapetype=Part.Solid" ); +#ifdef FC_USE_TNP_FIX add_keyword_method("makeRuledSurface",&Module::makeRuledSurface, "makeRuledSurface(Edge|Wire,Edge|Wire) -- Make a ruled surface\n" "Create a ruled surface out of two edges or wires. If wires are used then" @@ -531,6 +554,23 @@ public: "makeShellFromWires(Wires) -- Make a shell from wires.\n" "The wires must have the same number of edges." ); + add_keyword_method("makeLoft",&Module::makeLoft, + "makeLoft(list of wires,[solid=False,ruled=False,closed=False,maxDegree=5]) -- Create a loft shape." + ); +#else + add_varargs_method("makeRuledSurface",&Module::makeRuledSurface, + "makeRuledSurface(Edge|Wire,Edge|Wire) -- Make a ruled surface\n" + "Create a ruled surface out of two edges or wires. If wires are used then" + "these must have the same number of edges." + ); + add_varargs_method("makeShellFromWires",&Module::makeShellFromWires, + "makeShellFromWires(Wires) -- Make a shell from wires.\n" + "The wires must have the same number of edges." + ); + add_varargs_method("makeLoft",&Module::makeLoft, + "makeLoft(list of wires,[solid=False,ruled=False,closed=False,maxDegree=5]) -- Create a loft shape." + ); +#endif add_varargs_method("makeTube",&Module::makeTube, "makeTube(edge,radius,[continuity,max degree,max segments]) -- Create a tube.\n" "continuity is a string which must be 'C0','C1','C2','C3','CN','G1' or 'G1'," @@ -538,9 +578,6 @@ public: add_varargs_method("makeSweepSurface",&Module::makeSweepSurface, "makeSweepSurface(edge(path),edge(profile),[float]) -- Create a profile along a path." ); - add_keyword_method("makeLoft",&Module::makeLoft, - "makeLoft(list of wires,[solid=False,ruled=False,closed=False,maxDegree=5]) -- Create a loft shape." - ); add_varargs_method("makeWireString",&Module::makeWireString, "makeWireString(string,fontdir,fontfile,height,[track]) -- Make list of wires in the form of a string's characters." ); @@ -887,18 +924,36 @@ private: } return list; } - Py::Object makeCompound(const Py::Tuple& args) +#ifdef FC_USE_TNP_FIX + Py::Object makeCompound(const Py::Tuple& args, const Py::Dict &kwds) { -#ifndef FC_USE_TNP_FIX - PyObject *pcObj; - PyObject *force = Py_True; - const char *op = nullptr; - static char* kwd_list[] = {"shapes", "force", "op", nullptr}; - if(!PyArg_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O|O!s", kwd_list, &pcObj, &PyBool_Type, &force, &op)) + PyObject* pcObj; + PyObject* force = Py_True; + TopoShape::SingleShapeCompoundCreationPolicy + policy; // = TopoShape::SingleShapeCompoundCreationPolicy::forceCompound; + PyObject* module = PyImport_ImportModule("PartEnums"); + PyObject* policyEnum = + PyObject_GetAttrString(module, (char*)"SingleShapeCompoundCreationPolicy"); + const char* op = nullptr; + const std::array kwd_list = {"shapes", "force", "op", nullptr}; + if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), + kwds.ptr(), + "O|O!s", + kwd_list, + &pcObj, + policyEnum, + &force, + &op)) { throw Py::Exception(); + } - return shape2pyshape(Part::TopoShape().makECompound(getPyShapes(pcObj), op, PyObject_IsTrue(force))); + policy = static_cast(PyLong_AsLong(force)); + Py_DECREF(policyEnum); + + return shape2pyshape(Part::TopoShape().makeElementCompound(getPyShapes(pcObj), op, policy)); #else +Py::Object makeCompound(const Py::Tuple& args) +{ PyObject *pcObj; if (!PyArg_ParseTuple(args.ptr(), "O", &pcObj)) throw Py::Exception(); @@ -917,16 +972,25 @@ private: return Py::asObject(new TopoShapeCompoundPy(new TopoShape(Comp))); #endif } +#ifdef FC_USE_TNP_FIX Py::Object makeShell(const Py::Tuple& args, const Py::Dict &kwds) { -#ifdef FC_USE_TNP_FIX - PyObject *obj; - const char *op = nullptr; - const std::array kwd_list = {"shapes", "op", nullptr}; - if(!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O|s", kwd_list, &obj, &op)) + PyObject* obj; + const char* op = nullptr; + const std::array kwd_list = {"shapes", "op", nullptr}; + if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), + kwds.ptr(), + "O|s", + kwd_list, + &obj, + &op)) { throw Py::Exception(); - return shape2pyshape(Part::TopoShape().makeElementBoolean(Part::OpCodes::Shell,getPyShapes(obj),op)); + } + return shape2pyshape( + Part::TopoShape().makeElementBoolean(Part::OpCodes::Shell, getPyShapes(obj), op)); #else +Py::Object makeShell(const Py::Tuple& args) +{ PyObject *obj; if (!PyArg_ParseTuple(args.ptr(), "O", &obj)) throw Py::Exception(); @@ -962,18 +1026,26 @@ private: return Py::asObject(new TopoShapeShellPy(new TopoShape(shape))); #endif } +#ifdef FC_USE_TNP_FIX Py::Object makeFace(const Py::Tuple& args, const Py::Dict &kwds) { -#ifdef FC_USE_TNP_FIX - PyObject *obj; - const char *className = nullptr; - const char *op = nullptr; - const std::array kwd_list = {"shapes", "class_name", "op", nullptr}; - if(!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O|ss", kwd_list, - &obj, &className, &op)) + PyObject* obj; + const char* className = nullptr; + const char* op = nullptr; + const std::array kwd_list = {"shapes", "class_name", "op", nullptr}; + if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), + kwds.ptr(), + "O|ss", + kwd_list, + &obj, + &className, + &op)) { throw Py::Exception(); - return shape2pyshape(TopoShape().makeElementFace(getPyShapes(obj),op,className)); + } + return shape2pyshape(TopoShape().makeElementFace(getPyShapes(obj), op, className)); #else +Py::Object makeFace(const Py::Tuple& args) +{ try { char* className = nullptr; PyObject* pcPyShapeOrList = nullptr; @@ -1045,47 +1117,85 @@ private: } +#ifdef FC_USE_TNP_FIX Py::Object makeFilledSurface(const Py::Tuple &args, const Py::Dict &kwds) { -#ifdef FC_USE_TNP_FIX TopoShape::BRepFillingParams params; - PyObject *obj = nullptr; - PyObject *pySurface = Py_None; - PyObject *supports = Py_None; - PyObject *orders = Py_None; - PyObject *anisotropy = params.anisotropy ? Py_True : Py_False; - const char *op = nullptr; - const std::array kwd_list = {"shapes", "surface", "supports", - "orders","degree","ptsOnCurve","numIter","anisotropy", - "tol2d", "tol3d", "tolG1", "tolG2", "maxDegree", "maxSegments", "op", nullptr}; - if(!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O|O!OOIIIOddddIIs", kwd_list, - &obj, &pySurface, &orders, ¶ms.degree, ¶ms.ptsoncurve, - ¶ms.numiter, &anisotropy, ¶ms.tol2d, ¶ms.tol3d, - ¶ms.tolG1, ¶ms.tolG2, ¶ms.maxdeg, ¶ms.maxseg, &op)) + PyObject* obj = nullptr; + PyObject* pySurface = Py_None; + PyObject* supports = Py_None; + PyObject* orders = Py_None; + PyObject* anisotropy = params.anisotropy ? Py_True : Py_False; + const char* op = nullptr; + const std::array kwd_list = {"shapes", + "surface", + "supports", + "orders", + "degree", + "ptsOnCurve", + "numIter", + "anisotropy", + "tol2d", + "tol3d", + "tolG1", + "tolG2", + "maxDegree", + "maxSegments", + "op", + nullptr}; + if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), + kwds.ptr(), + "O|O!OOIIIOddddIIs", + kwd_list, + &obj, + &pySurface, + &orders, + ¶ms.degree, + ¶ms.ptsoncurve, + ¶ms.numiter, + &anisotropy, + ¶ms.tol2d, + ¶ms.tol3d, + ¶ms.tolG1, + ¶ms.tolG2, + ¶ms.maxdeg, + ¶ms.maxseg, + &op)) { throw Py::Exception(); + } params.anisotropy = PyObject_IsTrue(anisotropy); TopoShape surface; - if(pySurface != Py_None) + if (pySurface != Py_None) { surface = *static_cast(pySurface)->getTopoShapePtr(); - parseSequence(supports, "Expects 'supports' to be a sequence of tuple(shape, shape)", - [&](const TopoDS_Shape &s, PyObject *value, const char *err) { - if (!PyObject_TypeCheck(value, &(Part::TopoShapePy::Type))) - throw Py::TypeError(err); - params.supports[s] = static_cast(value)->getTopoShapePtr()->getShape(); + } + parseSequence(supports, + "Expects 'supports' to be a sequence of tuple(shape, shape)", + [&](const TopoDS_Shape& s, PyObject* value, const char* err) { + if (!PyObject_TypeCheck(value, &(Part::TopoShapePy::Type))) { + throw Py::TypeError(err); + } + params.supports[s] = + static_cast(value)->getTopoShapePtr()->getShape(); }); - parseSequence(orders, "Expects 'orders' to be a sequence of tuple(shape, PartEnums.Shape)", - [&](const TopoDS_Shape &s, PyObject *value, const char *err) { - if (!PyLong_Check(value)) - throw Py::ValueError(err); - int order = Py::Int(value); + parseSequence(orders, + "Expects 'orders' to be a sequence of tuple(shape, PartEnums.Shape)", + [&](const TopoDS_Shape& s, PyObject* value, const char* err) { + if (!PyLong_Check(value)) { + throw Py::ValueError(err); + } + int order = Py::Int(value); params.orders[s] = static_cast(order); return; }); auto shapes = getPyShapes(obj); - if (shapes.empty()) + if (shapes.empty()) { throw Py::ValueError("No input shape"); - return shape2pyshape(TopoShape(0, shapes.front().Hasher).makeElementFilledFace(shapes,params,op)); + } + return shape2pyshape( + TopoShape(0, shapes.front().Hasher).makeElementFilledFace(shapes, params, op)); #else +Py::Object makeFilledSurface(const Py::Tuple &args) +{ PyObject *obj; double tolerance; if (!PyArg_ParseTuple(args.ptr(), "Od", &obj, &tolerance)) @@ -1117,47 +1227,85 @@ private: } #endif } +#ifdef FC_USE_TNP_FIX Py::Object makeFilledFace(const Py::Tuple& args, const Py::Dict &kwds) { -#ifdef FC_USE_TNP_FIX TopoShape::BRepFillingParams params; - PyObject *obj = nullptr; - PyObject *pySurface = Py_None; - PyObject *supports = Py_None; - PyObject *orders = Py_None; - PyObject *anisotropy = params.anisotropy ? Py_True : Py_False; - const char *op = nullptr; - const std::array kwd_list = {"shapes", "surface", "supports", - "orders","degree","ptsOnCurve","numIter","anisotropy", - "tol2d", "tol3d", "tolG1", "tolG2", "maxDegree", "maxSegments", "op", nullptr}; - if(!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O|O!OOIIIOddddIIs", kwd_list, - &obj, &pySurface, &orders, ¶ms.degree, ¶ms.ptsoncurve, - ¶ms.numiter, &anisotropy, ¶ms.tol2d, ¶ms.tol3d, - ¶ms.tolG1, ¶ms.tolG2, ¶ms.maxdeg, ¶ms.maxseg, &op)) + PyObject* obj = nullptr; + PyObject* pySurface = Py_None; + PyObject* supports = Py_None; + PyObject* orders = Py_None; + PyObject* anisotropy = params.anisotropy ? Py_True : Py_False; + const char* op = nullptr; + const std::array kwd_list = {"shapes", + "surface", + "supports", + "orders", + "degree", + "ptsOnCurve", + "numIter", + "anisotropy", + "tol2d", + "tol3d", + "tolG1", + "tolG2", + "maxDegree", + "maxSegments", + "op", + nullptr}; + if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), + kwds.ptr(), + "O|O!OOIIIOddddIIs", + kwd_list, + &obj, + &pySurface, + &orders, + ¶ms.degree, + ¶ms.ptsoncurve, + ¶ms.numiter, + &anisotropy, + ¶ms.tol2d, + ¶ms.tol3d, + ¶ms.tolG1, + ¶ms.tolG2, + ¶ms.maxdeg, + ¶ms.maxseg, + &op)) { throw Py::Exception(); + } params.anisotropy = PyObject_IsTrue(anisotropy); TopoShape surface; - if(pySurface != Py_None) + if (pySurface != Py_None) { surface = *static_cast(pySurface)->getTopoShapePtr(); - parseSequence(supports, "Expects 'supports' to be a sequence of tuple(shape, shape)", - [&](const TopoDS_Shape &s, PyObject *value, const char *err) { - if (!PyObject_TypeCheck(value, &(Part::TopoShapePy::Type))) - throw Py::TypeError(err); - params.supports[s] = static_cast(value)->getTopoShapePtr()->getShape(); + } + parseSequence(supports, + "Expects 'supports' to be a sequence of tuple(shape, shape)", + [&](const TopoDS_Shape& s, PyObject* value, const char* err) { + if (!PyObject_TypeCheck(value, &(Part::TopoShapePy::Type))) { + throw Py::TypeError(err); + } + params.supports[s] = + static_cast(value)->getTopoShapePtr()->getShape(); }); - parseSequence(orders, "Expects 'orders' to be a sequence of tuple(shape, PartEnums.Shape)", - [&](const TopoDS_Shape &s, PyObject *value, const char *err) { - if (!PyLong_Check(value)) - throw Py::ValueError(err); - int order = Py::Int(value); + parseSequence(orders, + "Expects 'orders' to be a sequence of tuple(shape, PartEnums.Shape)", + [&](const TopoDS_Shape& s, PyObject* value, const char* err) { + if (!PyLong_Check(value)) { + throw Py::ValueError(err); + } + int order = Py::Int(value); params.orders[s] = static_cast(order); return; }); auto shapes = getPyShapes(obj); - if (shapes.empty()) + if (shapes.empty()) { throw Py::ValueError("No input shape"); - return shape2pyshape(TopoShape(0, shapes.front().Hasher).makeElementFilledFace(shapes,params,op)); + } + return shape2pyshape( + TopoShape(0, shapes.front().Hasher).makeElementFilledFace(shapes, params, op)); #else +Py::Object makeFilledFace(const Py::Tuple& args) +{ // TODO: BRepFeat_SplitShape PyObject *obj; PyObject *surf=nullptr; @@ -1216,17 +1364,26 @@ private: } #endif } +#ifdef FC_USE_TNP_FIX Py::Object makeSolid(const Py::Tuple& args, const Py::Dict &kwds) { -#ifndef FC_NO_ELEMENT_MAP - PyObject *obj; - const char *op = nullptr; - const std::array kwd_list = {"shape", "op", nullptr}; - if(!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!|s", - kwd_list, &(TopoShapePy::Type), &obj, &op)) + PyObject* obj; + const char* op = nullptr; + const std::array kwd_list = {"shape", "op", nullptr}; + if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), + kwds.ptr(), + "O!|s", + kwd_list, + &(TopoShapePy::Type), + &obj, + &op)) { throw Py::Exception(); - return shape2pyshape(TopoShape().makeElementSolid(*static_cast(obj)->getTopoShapePtr(),op)); + } + return shape2pyshape( + TopoShape().makeElementSolid(*static_cast(obj)->getTopoShapePtr(), op)); #else +Py::Object makeSolid(const Py::Tuple& args) +{ PyObject *obj; if (!PyArg_ParseTuple(args.ptr(), "O!", &(TopoShapePy::Type), &obj)) throw Py::Exception(); @@ -1831,25 +1988,37 @@ private: throw Py::Exception(PartExceptionOCCDomainError, "creation of revolved shape failed"); } } +#ifdef FC_USE_TNP_FIX Py::Object makeRuledSurface(const Py::Tuple& args, const Py::Dict &kwds) { -#ifdef FC_USE_TNP_FIX - const char *op=nullptr; + const char* op = nullptr; int orientation = 0; PyObject *sh1, *sh2; - const std::array kwd_list = {"path", "profile", "orientation", "op", nullptr}; - if(!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!O!|is", kwd_list, - &(TopoShapePy::Type), &sh1, - &(TopoShapePy::Type), &sh2, - &orientation, - &op)) + const std::array kwd_list = {"path", + "profile", + "orientation", + "op", + nullptr}; + if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), + kwds.ptr(), + "O!O!|is", + kwd_list, + &(TopoShapePy::Type), + &sh1, + &(TopoShapePy::Type), + &sh2, + &orientation, + &op)) { throw Py::Exception(); + } std::vector shapes; shapes.push_back(*static_cast(sh1)->getTopoShapePtr()); shapes.push_back(*static_cast(sh2)->getTopoShapePtr()); - return shape2pyshape(TopoShape().makeElementRuledSurface(shapes,orientation,op)); + return shape2pyshape(TopoShape().makeElementRuledSurface(shapes, orientation, op)); #else +Py::Object makeRuledSurface(const Py::Tuple& args) +{ // http://opencascade.blogspot.com/2009/10/surface-modeling-part1.html PyObject *sh1, *sh2; if (!PyArg_ParseTuple(args.ptr(), "O!O!", &(TopoShapePy::Type), &sh1, @@ -1877,20 +2046,31 @@ private: } #endif } +#ifdef FC_USE_TNP_FIX Py::Object makeShellFromWires(const Py::Tuple& args, const Py::Dict &kwds) { PyObject *pylist; -#ifdef FC_USE_TNP_FIX - const char *op = nullptr; - const std::array kwd_list = {"shape", "op", nullptr}; - if(!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O|s", kwd_list, &pylist, &op)) + const char* op = nullptr; + const std::array kwd_list = {"shape", "op", nullptr}; + if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), + kwds.ptr(), + "O|s", + kwd_list, + &pylist, + &op)) { throw Py::Exception(); + } try { - return shape2pyshape(TopoShape().makeElementShellFromWires(getPyShapes(pylist), /*silent*/false, op)); - } catch (Standard_Failure&) { + return shape2pyshape( + TopoShape().makeElementShellFromWires(getPyShapes(pylist), /*silent*/ false, op)); + } + catch (Standard_Failure&) { throw Py::Exception(PartExceptionOCCError, "creation of shell failed"); } #else +Py::Object makeShellFromWires(const Py::Tuple& args) +{ + PyObject *pylist; if (!PyArg_ParseTuple(args.ptr(), "O", &pylist)) throw Py::Exception(); @@ -1968,14 +2148,19 @@ private: throw Py::Exception(); try { -#ifndef FC_NO_ELEMENT_MAP +#ifdef FC_USE_TNP_FIX TopoShape mShape = *static_cast(path)->getTopoShapePtr(); // makeSweep uses GeomFill_Pipe which does not support shape // history. So use makEPipeShell() as a replacement - return shape2pyshape(TopoShape(0, mShape.Hasher).makeElementPipeShell( - {mShape, *static_cast(profile)->getTopoShapePtr()}, - Part::MakeSolid::noSolid, Standard_False, TransitionMode::Transformed, - nullptr, tolerance)); + return shape2pyshape( + TopoShape(0, mShape.Hasher) + .makeElementPipeShell( + {mShape, *static_cast(profile)->getTopoShapePtr()}, + Part::MakeSolid::noSolid, + Standard_False, + TransitionMode::Transformed, + nullptr, + tolerance)); #else const TopoDS_Shape& path_shape = static_cast(path)->getTopoShapePtr()->getShape(); const TopoDS_Shape& prof_shape = static_cast(profile)->getTopoShapePtr()->getShape(); @@ -1989,6 +2174,7 @@ private: throw Py::Exception(PartExceptionOCCError, e.GetMessageString()); } } +# ifdef FC_USE_TNP_FIX Py::Object makeLoft(const Py::Tuple& args, const Py::Dict &kwds) { PyObject *pcObj; @@ -1996,27 +2182,43 @@ private: PyObject *pruled=Py_False; PyObject *pclosed=Py_False; int degMax = 5; -# ifdef FC_USE_TNP_FIX - const char *op = nullptr; - const std::array kwd_list = {"shapes", "solid", "ruled", "closed", "max_degree", "op", nullptr}; - if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!|O!O!O!is", kwd_list, - &pcObj, - &(PyBool_Type), &psolid, - &(PyBool_Type), &pruled, - &(PyBool_Type), &pclosed, - °Max, &op)) - { + const char* op = nullptr; + const std::array kwd_list = + {"shapes", "solid", "ruled", "closed", "max_degree", "op", nullptr}; + if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), + kwds.ptr(), + "O!|O!O!O!is", + kwd_list, + &(PyList_Type), + &pcObj, + &(PyBool_Type), + &psolid, + &(PyBool_Type), + &pruled, + &(PyBool_Type), + &pclosed, + °Max, + &op)) { throw Py::Exception(); } Standard_Boolean anIsSolid = PyObject_IsTrue(psolid) ? Standard_True : Standard_False; Standard_Boolean anIsRuled = PyObject_IsTrue(pruled) ? Standard_True : Standard_False; Standard_Boolean anIsClosed = PyObject_IsTrue(pclosed) ? Standard_True : Standard_False; - return shape2pyshape(TopoShape().makeElementLoft(getPyShapes(pcObj), - anIsSolid ? Part::IsSolid::solid : Part::IsSolid::notSolid, - anIsRuled ? Part::IsRuled::ruled : Part::IsRuled::notRuled, - anIsClosed ? Part::IsClosed::closed : Part::IsClosed::notClosed, - degMax,op)); -# else + return shape2pyshape(TopoShape().makeElementLoft( + getPyShapes(pcObj), + anIsSolid ? Part::IsSolid::solid : Part::IsSolid::notSolid, + anIsRuled ? Part::IsRuled::ruled : Part::IsRuled::notRuled, + anIsClosed ? Part::IsClosed::closed : Part::IsClosed::notClosed, + degMax, + op)); +#else +Py::Object makeLoft(const Py::Tuple& args) +{ + PyObject *pcObj; + PyObject *psolid=Py_False; + PyObject *pruled=Py_False; + PyObject *pclosed=Py_False; + int degMax = 5; if (!PyArg_ParseTuple(args.ptr(), "O|O!O!O!i", &pcObj, &(PyBool_Type), &psolid, &(PyBool_Type), &pruled, @@ -2108,11 +2310,13 @@ private: MapperMaker mapper(splitShape); for (TopTools_ListIteratorOfListOfShape it(d); it.More(); it.Next()) { TopoShape s(0, sources.front().Hasher); - list1.append(shape2pyshape(s.makeShapeWithElementMap(it.Value(), mapper, sources, Part::OpCodes::Split))); + list1.append(shape2pyshape( + s.makeShapeWithElementMap(it.Value(), mapper, sources, Part::OpCodes::Split))); } for (TopTools_ListIteratorOfListOfShape it(l); it.More(); it.Next()) { TopoShape s(0, sources.front().Hasher); - list2.append(shape2pyshape(s.makeShapeWithElementMap(it.Value(), mapper, sources, Part::OpCodes::Split))); + list2.append(shape2pyshape( + s.makeShapeWithElementMap(it.Value(), mapper, sources, Part::OpCodes::Split))); } #else for (TopTools_ListIteratorOfListOfShape it(d); it.More(); it.Next()) { @@ -2456,10 +2660,12 @@ private: "needSubElement", "transform", "retType", "noElementMap", "refine", nullptr}; if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O!|sO!O!O!hO!O!", kwd_list, - &App::DocumentObjectPy::Type, &pObj, &subname, &Base::MatrixPy::Type, - &pyMat, - &PyBool_Type, &needSubElement, &PyBool_Type, &transform, &retType, - &PyBool_Type, &noElementMap, &PyBool_Type, &refine)) { + &App::DocumentObjectPy::Type, &pObj, &subname, + &Base::MatrixPy::Type, &pyMat, + &PyBool_Type, &needSubElement, + &PyBool_Type, &transform, &retType, + &PyBool_Type, &noElementMap, + &PyBool_Type, &refine)) { throw Py::Exception(); } @@ -2473,8 +2679,8 @@ private: &mat,&subObj,retType==2,Base::asBoolean(transform), Base::asBoolean(noElementMap)); if (Base::asBoolean(refine)) { -#ifndef FC_NO_ELEMENT_MAP - shape = TopoShape(0,shape.Hasher).makeElementRefine(shape); +#ifdef FC_USE_TNP_FIX + shape = TopoShape(0, shape.Hasher).makeElementRefine(shape); #else BRepBuilderAPI_RefineModel mkRefine(shape.getShape()); shape.setShape(mkRefine.Shape()); diff --git a/src/Mod/Part/App/FeaturePartBox.cpp b/src/Mod/Part/App/FeaturePartBox.cpp index fb48eb9288..b3ee6fe9ef 100644 --- a/src/Mod/Part/App/FeaturePartBox.cpp +++ b/src/Mod/Part/App/FeaturePartBox.cpp @@ -71,7 +71,7 @@ App::DocumentObjectExecReturn *Box::execute() // Build a box using the dimension attributes BRepPrimAPI_MakeBox mkBox(L, W, H); TopoDS_Shape ResultShape = mkBox.Shape(); - this->Shape.setValue(ResultShape); + this->Shape.setValue(ResultShape, false); return Primitive::execute(); } catch (Standard_Failure& e) { diff --git a/src/Mod/Part/App/TopoShapePyImp.cpp b/src/Mod/Part/App/TopoShapePyImp.cpp index 406a9297d4..8d9bf124c0 100644 --- a/src/Mod/Part/App/TopoShapePyImp.cpp +++ b/src/Mod/Part/App/TopoShapePyImp.cpp @@ -2925,47 +2925,47 @@ getElements(const TopoShape& sh, TopAbs_ShapeEnum type, TopAbs_ShapeEnum avoid = // }PY_CATCH_OCC; //} -Py::List TopoShapePy::getSubShapes(void) const +Py::List TopoShapePy::getSubShapes() const { return getElements(*getTopoShapePtr(), TopAbs_SHAPE); } -Py::List TopoShapePy::getFaces(void) const +Py::List TopoShapePy::getFaces() const { return getElements(*getTopoShapePtr(), TopAbs_FACE); } -Py::List TopoShapePy::getVertexes(void) const +Py::List TopoShapePy::getVertexes() const { return getElements(*getTopoShapePtr(), TopAbs_VERTEX); } -Py::List TopoShapePy::getShells(void) const +Py::List TopoShapePy::getShells() const { return getElements(*getTopoShapePtr(), TopAbs_SHELL); } -Py::List TopoShapePy::getSolids(void) const +Py::List TopoShapePy::getSolids() const { return getElements(*getTopoShapePtr(), TopAbs_SOLID); } -Py::List TopoShapePy::getCompSolids(void) const +Py::List TopoShapePy::getCompSolids() const { return getElements(*getTopoShapePtr(), TopAbs_COMPSOLID); } -Py::List TopoShapePy::getEdges(void) const +Py::List TopoShapePy::getEdges() const { return getElements(*getTopoShapePtr(), TopAbs_EDGE); } -Py::List TopoShapePy::getWires(void) const +Py::List TopoShapePy::getWires() const { return getElements(*getTopoShapePtr(), TopAbs_WIRE); } -Py::List TopoShapePy::getCompounds(void) const +Py::List TopoShapePy::getCompounds() const { return getElements(*getTopoShapePtr(), TopAbs_COMPOUND); } diff --git a/src/Mod/Part/PartEnums.py b/src/Mod/Part/PartEnums.py index 53c0562cfa..4eddcf2b8e 100644 --- a/src/Mod/Part/PartEnums.py +++ b/src/Mod/Part/PartEnums.py @@ -71,3 +71,7 @@ class HLRBRep_TypeOfResultingEdge(IntEnum): RgNLine = 4 Sharp = 5 +class SingleShapeCompoundCreationPolicy(IntEnum): + ReturnShape = 0 + ForceCompound = 1 + diff --git a/src/Mod/Part/TestPartApp.py b/src/Mod/Part/TestPartApp.py index b1f4e92fed..0dedc5a1da 100644 --- a/src/Mod/Part/TestPartApp.py +++ b/src/Mod/Part/TestPartApp.py @@ -467,7 +467,7 @@ class PartTestRuledSurface(unittest.TestCase): self.assertEqual(len(same1), 2) self.assertEqual(len(same2), 2) - def testRuledSurfaceFromOneObjects(self): + def testRuledSurfaceFromOneObject(self): sketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch') sketch.Placement = FreeCAD.Placement(FreeCAD.Vector(0.000000, 0.000000, 0.000000), App.Rotation(0.707107, 0.000000, 0.000000, 0.707107)) sketch.MapMode = "Deactivated" diff --git a/src/Mod/Part/parttests/TopoShapeTest.py b/src/Mod/Part/parttests/TopoShapeTest.py index 57861cf132..e102ac6dca 100644 --- a/src/Mod/Part/parttests/TopoShapeTest.py +++ b/src/Mod/Part/parttests/TopoShapeTest.py @@ -4,6 +4,7 @@ import Part import unittest class TopoShapeAssertions: + def assertAttrEqual(self, toposhape, attr_value_list, msg=None): for attr, value in attr_value_list: result = toposhape.__getattribute__( @@ -45,10 +46,22 @@ class TopoShapeTest(unittest.TestCase, TopoShapeAssertions): def setUp(self): """Create a document and some TopoShapes of various types""" self.doc = App.newDocument("TopoShape") - self.box = Part.makeBox(1, 2, 2) - Part.show(self.box, "Box1") - self.box2 = Part.makeBox(2, 1, 2) - Part.show(self.box2, "Box2") + # self.box = Part.makeBox(1, 2, 2) + # Part.show(self.box, "Box1") + # self.box2 = Part.makeBox(2, 1, 2) + # Part.show(self.box2, "Box2") + # Even on LS3 these boxes have no element maps. + self.doc.addObject("Part::Box", "Box1") + self.doc.Box1.Length = 1 + self.doc.Box1.Width = 2 + self.doc.Box1.Height = 2 + self.doc.addObject("Part::Box", "Box2") + self.doc.Box2.Length = 2 + self.doc.Box2.Width = 1 + self.doc.Box2.Height = 2 + self.doc.recompute() + self.box = self.doc.Box1.Shape + self.box2 = self.doc.Box2.Shape def tearDown(self): App.closeDocument("TopoShape") @@ -155,39 +168,15 @@ class TopoShapeTest(unittest.TestCase, TopoShapeAssertions): # Todo: This should contain something as soon as the Python interface for Part.Compound TNP exists # self.assertEqual(len(compound1.ElementMap), 52, "ElementMap is Incorrect: {0}".format(compound1.ElementMap)) self.assertEqual( - len(compound2.ElementReverseMap), + compound2.ElementMapSize, 52, "ElementMap is Incorrect: {0}".format(compound2.ElementMap), ) - # def testTopoShapeOperations(self): - # compound1 = Part.Compound([self.doc.Box1.Shape, self.doc.Box2.Shape]) - # box1ts = self.doc.Box1.Shape - # box2ts = self.doc.Box2.Shape - # face1 = Part.Face(Part.Wire([Part.makeCircle(10)])) - # cut1 = box1ts.cut(box2ts) - # common1 = box1ts.common(box2ts) - # fuse1 = box1ts.fuse(box2ts) - # fuse2 = box1ts.generalFuse([box2ts]) - # fuse3 = box1ts.multiFuse([box2ts]) - # mirror1 = box1ts.mirror(App.Vector(0, 0, 0), App.Vector(1, 0, 0)) - # clean1 = box1ts.cleaned() - # # complement1 = box1ts.complement() - # # fix1 = box1ts.fix() - # rotate1 = box1ts.rotated(App.Vector(0, 0, 0), App.Vector(1, 0, 0), 45) - # scale1 = box1ts.scaled(2) - # translate1 = box1ts.translated((2, 0, 0)) - # section1 = box1ts.section(face1) - # slice1 = box1ts.slice(App.Vector(1, 0, 0), 2) - # slice2 = box1ts.slices(App.Vector(1, 0, 0), [2, 3]) - # # clean, complement, fix, reverse, scale, - def testPartCommon(self): self.doc.addObject("Part::MultiCommon", "Common") self.doc.Common.Shapes = [self.doc.Box1, self.doc.Box2] self.doc.recompute() - names = list(self.doc.Common.Shape.ElementReverseMap.keys()) - names.sort() if self.doc.Common.Shape.ElementMapVersion != "": # Should be '4' as of Mar 2023. self.assertKeysInMap(self.doc.Common.Shape.ElementReverseMap, [ @@ -263,12 +252,71 @@ class TopoShapeTest(unittest.TestCase, TopoShapeAssertions): self.doc.Fuse.Tool = self.doc.Box2 self.doc.recompute() if self.doc.Fuse.Shape.ElementMapVersion != "": # Should be '4' as of Mar 2023. - self.assertEqual(len(self.doc.Fuse.Shape.ElementReverseMap), 58) + self.assertEqual(self.doc.Fuse.Shape.ElementMapSize, 58) self.doc.Fuse.Refine = True self.doc.recompute() - self.assertEqual(len(self.doc.Fuse.Shape.ElementReverseMap), 38) + self.assertEqual(self.doc.Fuse.Shape.ElementMapSize, 38) # Shape is an extruded L, with 8 Faces, 12 Vertexes, 18 Edges + def testAppPartmakeCompound(self): + # This doesn't do element maps. + # compound1 = Part.Compound([self.doc.Box1.Shape, self.doc.Box2.Shape]) + compound1 = Part.makeCompound([self.doc.Box1.Shape, self.doc.Box2.Shape]) + if compound1.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(compound1.ElementMapSize, 52) + + def testAppPartmakeShell(self): + shell1 = Part.makeShell(self.doc.Box1.Shape.Faces) + if shell1.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(shell1.ElementMapSize, 26) + + def testAppPartmakeFace(self): + face1 = Part.makeFace(self.doc.Box1.Shape.Faces[0],"Part::FaceMakerCheese") + if face1.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(face1.ElementMapSize, 10) + + def testAppPartmakeFilledFace(self): + face1 = Part.makeFilledFace(self.doc.Box1.Shape.Faces[3].Edges) + if face1.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(face1.ElementMapSize, 9) + + def testAppPartmakeSolid(self): + solid1 = Part.makeSolid(self.doc.Box1.Shape.Shells[0]) + if solid1.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(solid1.ElementMapSize, 26) + + def testAppPartmakeRuled(self): + surface1 = Part.makeRuledSurface(*self.doc.Box1.Shape.Edges[3:5]) + if surface1.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(surface1.ElementMapSize, 9) + + def testAppPartmakeShellFromWires(self): + wire1 = self.doc.Box1.Shape.Wires[0] #.copy() Todo: prints double generated/modified warning because + wire2 = self.doc.Box1.Shape.Wires[1] #.copy() Todo: copy() isn't TNP ready yet. Fix when it is. + shell1 = Part.makeShellFromWires([wire1,wire2]) + if shell1.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(shell1.ElementMapSize, 24) + + def testAppPartmakeSweepSurface(self): + pass # Todo: This is already fixed in a future commit + # surface1 = Part.makeSweepSurface(*self.doc.Box1.Shape.Faces[3].Edges[0:2],1) + # if surface1.ElementMapVersion != "": # Should be '4' as of Mar 2023. + # self.assertEqual(surface1.ElementMapSize, 7) + + def testAppPartmakeLoft(self): + solid2 = Part.makeLoft(self.doc.Box1.Shape.Wires[0:2]) + if solid2.ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(solid2.ElementMapSize, 24) + + def testAppPartmakeSplitShape(self): + # Todo: Refine this test after all TNP code in place to elimate warnings. + edge1 = self.doc.Box1.Shape.Faces[0].Edges[0].translated(App.Vector(0,0.5,0)) + face1 = self.doc.Box1.Shape.Faces[0] + solids1 = Part.makeSplitShape(face1,[(edge1,face1)]) + if solids1[0][0].ElementMapVersion != "": # Should be '4' as of Mar 2023. + self.assertEqual(solids1[0][0].ElementMapSize, 9) + self.assertEqual(solids1[1][0].ElementMapSize, 9) + # TODO: Consider the following possible test objects: # Part::AttachExtension ::init();