diff --git a/src/Mod/Part/App/AppPartPy.cpp b/src/Mod/Part/App/AppPartPy.cpp index 367b7d8bc1..83246b9ecc 100644 --- a/src/Mod/Part/App/AppPartPy.cpp +++ b/src/Mod/Part/App/AppPartPy.cpp @@ -103,6 +103,8 @@ #include "TopoShapeShellPy.h" #include "TopoShapeSolidPy.h" #include "TopoShapeWirePy.h" +#include "TopoShapeOpCode.h" +#include "TopoShapeMapper.h" #ifdef FCUseFreeType # include "FT2FC.h" @@ -421,25 +423,47 @@ 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_varargs_method("makeShell",&Module::makeShell, + add_keyword_method("makeShell",&Module::makeShell, "makeShell(list) -- Create a shell out of a list of faces." ); - add_varargs_method("makeFace",&Module::makeFace, + add_keyword_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, + add_keyword_method("makeFilledSurface",&Module::makeFilledSurface, "makeFilledSurface(list of curves, tolerance) -- Create a surface out of a list of curves." ); - add_varargs_method("makeFilledFace",&Module::makeFilledFace, + add_keyword_method("makeFilledFace",&Module::makeFilledFace, "makeFilledFace(list) -- Create a face out of a list of edges." ); - add_varargs_method("makeSolid",&Module::makeSolid, + 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" @@ -520,15 +544,33 @@ 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" ); - add_varargs_method("makeRuledSurface",&Module::makeRuledSurface, +#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" "these must have the same number of edges." ); - add_varargs_method("makeShellFromWires",&Module::makeShellFromWires, + add_keyword_method("makeShellFromWires",&Module::makeShellFromWires, "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'," @@ -536,9 +578,6 @@ public: add_varargs_method("makeSweepSurface",&Module::makeSweepSurface, "makeSweepSurface(edge(path),edge(profile),[float]) -- Create a profile along a path." ); - add_varargs_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." ); @@ -885,8 +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) { + 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(); + } + + 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(); @@ -903,9 +970,27 @@ private: } } _PY_CATCH_OCC(throw Py::Exception()) return Py::asObject(new TopoShapeCompoundPy(new TopoShape(Comp))); +#endif } - Py::Object makeShell(const Py::Tuple& args) +#ifdef FC_USE_TNP_FIX + Py::Object makeShell(const Py::Tuple& args, const Py::Dict &kwds) { + 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)); +#else +Py::Object makeShell(const Py::Tuple& args) +{ PyObject *obj; if (!PyArg_ParseTuple(args.ptr(), "O", &obj)) throw Py::Exception(); @@ -939,9 +1024,28 @@ private: } return Py::asObject(new TopoShapeShellPy(new TopoShape(shape))); +#endif } - Py::Object makeFace(const Py::Tuple& args) +#ifdef FC_USE_TNP_FIX + Py::Object makeFace(const Py::Tuple& args, const Py::Dict &kwds) { + 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)); +#else +Py::Object makeFace(const Py::Tuple& args) +{ try { char* className = nullptr; PyObject* pcPyShapeOrList = nullptr; @@ -987,9 +1091,111 @@ private: e.setPyException(); throw Py::Exception(); } +#endif } - Py::Object makeFilledSurface(const Py::Tuple &args) + + template + void parseSequence(PyObject *pyObj, const char *err, F f) { + if (pyObj != Py_None) { + if (!PySequence_Check(pyObj)) + throw Py::TypeError(err); + Py::Sequence seq(pyObj); + for (Py::Sequence::iterator it = seq.begin(); it != seq.end(); ++it) { + if (!PySequence_Check((*it).ptr())) + throw Py::TypeError(err); + Py::Sequence tuple((*it).ptr()); + if (tuple.size() != 2) + throw Py::TypeError(err); + auto iter = tuple.begin(); + if (!PyObject_TypeCheck((*iter).ptr(), &(Part::TopoShapePy::Type))) + throw Py::TypeError(err); + const TopoDS_Shape& sh = static_cast((*iter).ptr())->getTopoShapePtr()->getShape(); + f(sh, (*iter).ptr(), err); + } + } + } + + +#ifdef FC_USE_TNP_FIX + Py::Object makeFilledSurface(const Py::Tuple &args, const Py::Dict &kwds) + { + 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)) { + throw Py::Exception(); + } + params.anisotropy = PyObject_IsTrue(anisotropy); + TopoShape surface; + 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(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()) { + throw Py::ValueError("No input shape"); + } + 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)) @@ -1019,9 +1225,87 @@ private: catch (Standard_Failure& e) { throw Py::Exception(PartExceptionOCCError, e.GetMessageString()); } +#endif } - Py::Object makeFilledFace(const Py::Tuple& args) +#ifdef FC_USE_TNP_FIX + Py::Object makeFilledFace(const Py::Tuple& args, const Py::Dict &kwds) { + 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)) { + throw Py::Exception(); + } + params.anisotropy = PyObject_IsTrue(anisotropy); + TopoShape surface; + 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(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()) { + throw Py::ValueError("No input shape"); + } + 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; @@ -1078,9 +1362,28 @@ private: catch (Standard_Failure& e) { throw Py::Exception(PartExceptionOCCError, e.GetMessageString()); } +#endif } - Py::Object makeSolid(const Py::Tuple& args) +#ifdef FC_USE_TNP_FIX + Py::Object makeSolid(const Py::Tuple& args, const Py::Dict &kwds) { + 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)); +#else +Py::Object makeSolid(const Py::Tuple& args) +{ PyObject *obj; if (!PyArg_ParseTuple(args.ptr(), "O!", &(TopoShapePy::Type), &obj)) throw Py::Exception(); @@ -1128,6 +1431,7 @@ private: errmsg << "Creation of solid failed: " << err.GetMessageString(); throw Py::Exception(PartExceptionOCCError, errmsg.str().c_str()); } +#endif } Py::Object makePlane(const Py::Tuple& args) { @@ -1684,8 +1988,37 @@ private: throw Py::Exception(PartExceptionOCCDomainError, "creation of revolved shape failed"); } } - Py::Object makeRuledSurface(const Py::Tuple& args) +#ifdef FC_USE_TNP_FIX + Py::Object makeRuledSurface(const Py::Tuple& args, const Py::Dict &kwds) { + 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)) { + 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)); +#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, @@ -1711,10 +2044,33 @@ private: catch (Standard_Failure&) { throw Py::Exception(PartExceptionOCCError, "creation of ruled surface failed"); } +#endif } - Py::Object makeShellFromWires(const Py::Tuple& args) +#ifdef FC_USE_TNP_FIX + Py::Object makeShellFromWires(const Py::Tuple& args, const Py::Dict &kwds) { PyObject *pylist; + 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&) { + 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(); @@ -1735,6 +2091,7 @@ private: catch (Standard_Failure&) { throw Py::Exception(PartExceptionOCCError, "creation of shell failed"); } +#endif } Py::Object makeTube(const Py::Tuple& args) { @@ -1791,24 +2148,77 @@ private: throw Py::Exception(); try { +#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)); +#else const TopoDS_Shape& path_shape = static_cast(path)->getTopoShapePtr()->getShape(); const TopoDS_Shape& prof_shape = static_cast(profile)->getTopoShapePtr()->getShape(); TopoShape myShape(path_shape); TopoDS_Shape face = myShape.makeSweep(prof_shape, tolerance, fillMode); return Py::asObject(new TopoShapeFacePy(new TopoShape(face))); +#endif } catch (Standard_Failure& e) { throw Py::Exception(PartExceptionOCCError, e.GetMessageString()); } } - Py::Object makeLoft(const Py::Tuple& args) +# ifdef FC_USE_TNP_FIX + Py::Object makeLoft(const Py::Tuple& args, const Py::Dict &kwds) { PyObject *pcObj; PyObject *psolid=Py_False; PyObject *pruled=Py_False; PyObject *pclosed=Py_False; int degMax = 5; + 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 +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, @@ -1834,7 +2244,7 @@ private: Standard_Boolean anIsClosed = Base::asBoolean(pclosed); TopoDS_Shape aResult = myShape.makeLoft(profiles, anIsSolid, anIsRuled, anIsClosed, degMax); return Py::asObject(new TopoShapePy(new TopoShape(aResult))); - +#endif } Py::Object makeSplitShape(const Py::Tuple& args) { @@ -1846,6 +2256,8 @@ private: throw Py::Exception(); try { + std::vector sources; + sources.push_back(*static_cast(shape)->getTopoShapePtr()); TopoDS_Shape initShape = static_cast (shape)->getTopoShapePtr()->getShape(); BRepFeat_SplitShape splitShape(initShape); @@ -1856,6 +2268,7 @@ private: Py::Tuple tuple(*it); Py::TopoShape sh1(tuple[0]); Py::TopoShape sh2(tuple[1]); + sources.push_back(*sh1.extensionObject()->getTopoShapePtr()); const TopoDS_Shape& shape1= sh1.extensionObject()->getTopoShapePtr()->getShape(); const TopoDS_Shape& shape2= sh2.extensionObject()->getTopoShapePtr()->getShape(); if (shape1.IsNull() || shape2.IsNull()) @@ -1892,15 +2305,28 @@ private: const TopTools_ListOfShape& l = splitShape.Left(); Py::List list1; + Py::List list2; +#ifdef FC_USE_TNP_FIX + 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))); + } + 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))); + } +#else for (TopTools_ListIteratorOfListOfShape it(d); it.More(); it.Next()) { list1.append(shape2pyshape(it.Value())); } - Py::List list2; for (TopTools_ListIteratorOfListOfShape it(l); it.More(); it.Next()) { list2.append(shape2pyshape(it.Value())); } - +#endif Py::Tuple tuple(2); tuple.setItem(0, list1); tuple.setItem(1, list2); @@ -2234,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(); } @@ -2251,9 +2679,12 @@ private: &mat,&subObj,retType==2,Base::asBoolean(transform), Base::asBoolean(noElementMap)); if (Base::asBoolean(refine)) { - // shape = TopoShape(0,shape.Hasher).makERefine(shape); +#ifdef FC_USE_TNP_FIX + shape = TopoShape(0, shape.Hasher).makeElementRefine(shape); +#else BRepBuilderAPI_RefineModel mkRefine(shape.getShape()); shape.setShape(mkRefine.Shape()); +#endif } Py::Object sret(shape2pyshape(shape)); if(retType==0) 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/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();