From 8c6b4373146f352cdf46d231cbf7da6806042d23 Mon Sep 17 00:00:00 2001 From: PaddleStroke Date: Fri, 8 Nov 2024 10:57:18 +0100 Subject: [PATCH] Sketcher: Intersection externals --- src/Mod/Sketcher/App/SketchObject.cpp | 403 +++++++++++------- src/Mod/Sketcher/App/SketchObject.h | 19 +- src/Mod/Sketcher/App/SketchObjectPy.xml | 4 +- src/Mod/Sketcher/App/SketchObjectPyImp.cpp | 30 +- src/Mod/Sketcher/Gui/CommandAlterGeometry.cpp | 2 + src/Mod/Sketcher/Gui/CommandCreateGeo.cpp | 99 ++++- .../Sketcher/Gui/DrawSketchHandlerExternal.h | 15 +- src/Mod/Sketcher/Gui/Resources/Sketcher.qrc | 3 + .../icons/geometry/Sketcher_Intersection.svg | 399 +++++++++++++++++ .../geometry/Sketcher_Intersection_Constr.svg | 399 +++++++++++++++++ ...Sketcher_Pointer_External_Intersection.svg | 101 +++++ src/Mod/Sketcher/Gui/Workbench.cpp | 3 +- 12 files changed, 1309 insertions(+), 168 deletions(-) create mode 100644 src/Mod/Sketcher/Gui/Resources/icons/geometry/Sketcher_Intersection.svg create mode 100644 src/Mod/Sketcher/Gui/Resources/icons/geometry/Sketcher_Intersection_Constr.svg create mode 100644 src/Mod/Sketcher/Gui/Resources/icons/pointers/Sketcher_Pointer_External_Intersection.svg diff --git a/src/Mod/Sketcher/App/SketchObject.cpp b/src/Mod/Sketcher/App/SketchObject.cpp index 3c4424182e..67b1682ec8 100644 --- a/src/Mod/Sketcher/App/SketchObject.cpp +++ b/src/Mod/Sketcher/App/SketchObject.cpp @@ -141,6 +141,11 @@ SketchObject::SketchObject() "Sketch", (App::PropertyType)(App::Prop_None | App::Prop_ReadOnly), "Sketch external geometry"); + ADD_PROPERTY_TYPE(ExternalTypes, + ({}), + "Sketch", + (App::PropertyType)(App::Prop_None | App::Prop_Hidden), + "Sketch external geometry type: 0 = projection, 1 = intersection, 2 = both."); ADD_PROPERTY_TYPE(FullyConstrained, (false), "Sketch", @@ -7721,8 +7726,12 @@ int SketchObject::addExternal(App::DocumentObject *Obj, const char* SubName, boo } // get the actual lists of the externals + std::vector Types = ExternalTypes.getValues(); std::vector Objects = ExternalGeometry.getValues(); std::vector SubElements = ExternalGeometry.getSubValues(); + if (Types.size() != Objects.size()) { + Types.resize(Objects.size(), 0); + } const std::vector originalObjects = Objects; const std::vector originalSubElements = SubElements; @@ -7734,21 +7743,35 @@ int SketchObject::addExternal(App::DocumentObject *Obj, const char* SubName, boo return -1; } + bool add = true; for (size_t i = 0; i < Objects.size(); ++i) { if (Objects[i] == Obj && std::string(SubName) == SubElements[i]) { - Base::Console().Error("Link to %s already exists in this sketch.\n", SubName); - return -1; + if (Types[i] == (int)ExtType::Both + || (Types[i] == (int)ExtType::Projection && !intersection) + || (Types[i] == (int)ExtType::Intersection && intersection)) { + Base::Console().Error("Link to %s already exists in this sketch.\n", SubName); + return -1; + } + // Case where projections are already there when adding intersections. + add = false; + Types[i] = (int)ExtType::Both; } } + if (add) { + // add the new ones + Objects.push_back(Obj); + SubElements.emplace_back(SubName); + Types.push_back((int)(intersection ? ExtType::Intersection : ExtType::Projection)); + if (intersection) {} - // add the new ones - Objects.push_back(Obj); - SubElements.emplace_back(SubName); + // set the Link list. + ExternalGeometry.setValues(Objects, SubElements); + } + ExternalTypes.setValues(Types); - // set the Link list. - ExternalGeometry.setValues(Objects, SubElements); try { - rebuildExternalGeometry(defining, intersection); + ExternalToAdd ext{ Obj, std::string(SubName), defining, intersection }; + rebuildExternalGeometry(ext); } catch (const Base::Exception& e) { Base::Console().Error("%s\n", e.what()); @@ -8731,6 +8754,8 @@ void processEdge(const TopoDS_Edge& edge, } else if (curve.GetType() == GeomAbs_Ellipse) { + gp_Pnt P1 = curve.Value(curve.FirstParameter()); + gp_Pnt P2 = curve.Value(curve.LastParameter()); gp_Elips elipsOrig = curve.Ellipse(); gp_Elips elipsDest; gp_Pnt origCenter = elipsOrig.Location(); @@ -8792,11 +8817,22 @@ void processEdge(const TopoDS_Edge& edge, // projection is a circle if ((RDest - rDest) < (double)Precision::Confusion()) { - Handle(Geom_Circle) curve = new Geom_Circle(destCurveAx2, 0.5 * (rDest + RDest)); - auto* circle = new Part::GeomCircle(); - circle->setHandle(curve); - GeometryFacade::setConstruction(circle, true); - geos.emplace_back(circle); + Handle(Geom_Circle) curve2 = new Geom_Circle(destCurveAx2, 0.5 * (rDest + RDest)); + if (P1.SquareDistance(P2) < Precision::Confusion()) { + auto* circle = new Part::GeomCircle(); + circle->setHandle(curve2); + GeometryFacade::setConstruction(circle, true); + geos.emplace_back(circle); + } + else { + auto* arc = new Part::GeomArcOfCircle(); + Handle(Geom_TrimmedCurve) tCurve = new Geom_TrimmedCurve(curve2, + curve.FirstParameter(), + curve.LastParameter()); + arc->setHandle(tCurve); + GeometryFacade::setConstruction(arc, true); + geos.emplace_back(arc); + } } else { if (sketchPlane.Position().Direction().IsNormal( @@ -8818,11 +8854,23 @@ void processEdge(const TopoDS_Edge& edge, elipsDest.SetMinorRadius(destAxisMinor.Magnitude()); - Handle(Geom_Ellipse) curve = new Geom_Ellipse(elipsDest); - auto* ellipse = new Part::GeomEllipse(); - ellipse->setHandle(curve); - GeometryFacade::setConstruction(ellipse, true); - geos.emplace_back(ellipse); + if (P1.SquareDistance(P2) < Precision::Confusion()) { + Handle(Geom_Ellipse) curve = new Geom_Ellipse(elipsDest); + auto* ellipse = new Part::GeomEllipse(); + ellipse->setHandle(curve); + GeometryFacade::setConstruction(ellipse, true); + geos.emplace_back(ellipse); + } + else { + auto* aoe = new Part::GeomArcOfEllipse(); + Handle(Geom_Curve) curve2 = new Geom_Ellipse(elipsDest); + Handle(Geom_TrimmedCurve) tCurve = new Geom_TrimmedCurve(curve2, + curve.FirstParameter(), + curve.LastParameter()); + aoe->setHandle(tCurve); + GeometryFacade::setConstruction(aoe, true); + geos.emplace_back(aoe); + } } } } @@ -8929,15 +8977,19 @@ std::vector projectShape(const TopoDS_Shape& inShape, const gp_Ax3 } } -void SketchObject::rebuildExternalGeometry(bool defining, bool addIntersection) +void SketchObject::rebuildExternalGeometry(std::optional extToAdd) { Base::StateLocker lock(managedoperation, true); // no need to check input data validity as this is an sketchobject managed operation. // get the actual lists of the externals + auto Types = ExternalTypes.getValues(); auto Objects = ExternalGeometry.getValues(); auto SubElements = ExternalGeometry.getSubValues(); assert(externalGeoRef.size() == Objects.size()); auto keys = externalGeoRef; + if (Types.size() != Objects.size()) { + Types.resize(Objects.size(), 0); + } // re-check for any missing geometry element. The code here has a side // effect that the linked external geometry will continue to work even if @@ -9007,169 +9059,211 @@ void SketchObject::rebuildExternalGeometry(bool defining, bool addIntersection) const std::string &SubElement=SubElements[i]; const std::string &key = keys[i]; + bool beingCreated = false; + if (extToAdd) { + beingCreated = extToAdd->obj == Obj && extToAdd->subname == SubElement; + } + + bool projection = Types[i] == (int)ExtType::Projection || Types[i] == (int)ExtType::Both; + bool intersection = Types[i] == (int)ExtType::Intersection || Types[i] == (int)ExtType::Both; + // Skip frozen geometries bool frozen = false; bool sync = false; - bool intersection = addIntersection && (i+1 == (int)Objects.size()); for(auto id : externalGeoRefMap[key]) { auto it = externalGeoMap.find(id); if(it != externalGeoMap.end()) { auto egf = ExternalGeometryFacade::getFacade(ExternalGeo[it->second]); - if(egf->testFlag(ExternalGeometryExtension::Frozen)) + if(egf->testFlag(ExternalGeometryExtension::Frozen)) { frozen = true; - if(egf->testFlag(ExternalGeometryExtension::Sync)) + } + if (egf->testFlag(ExternalGeometryExtension::Sync)) { sync = true; + } } } if(frozen && !sync) { refSet.insert(std::move(key)); continue; } - if(!Obj || !Obj->getNameInDocument()) + if (!Obj || !Obj->getNameInDocument()) { continue; + } std::vector > geos; - try { - TopoDS_Shape refSubShape; + auto importVertex = [&](const TopoDS_Shape& refSubShape) { + gp_Pnt P = BRep_Tool::Pnt(TopoDS::Vertex(refSubShape)); + GeomAPI_ProjectPointOnSurf proj(P, gPlane); + P = proj.NearestPoint(); + Base::Vector3d p(P.X(), P.Y(), P.Z()); + invPlm.multVec(p, p); - if (Obj->isDerivedFrom()) { - auto* datum = static_cast(Obj); - refSubShape = datum->getShape(); - } - else if (Obj->isDerivedFrom()) { - try { + Part::GeomPoint* point = new Part::GeomPoint(p); + GeometryFacade::setConstruction(point, true); + geos.emplace_back(point); + }; + + try { + TopoDS_Shape refSubShape; + + if (Obj->isDerivedFrom()) { + auto* datum = static_cast(Obj); + refSubShape = datum->getShape(); + } + else if (Obj->isDerivedFrom()) { auto* refObj = static_cast(Obj); const Part::TopoShape& refShape = refObj->Shape.getShape(); refSubShape = refShape.getSubShape(SubElement.c_str()); } - catch (Standard_Failure& e) { - throw Base::CADKernelError(e.GetMessageString()); + else if (Obj->isDerivedFrom()) { + auto* pl = static_cast(Obj); + Base::Placement plm = pl->Placement.getValue(); + Base::Vector3d base = plm.getPosition(); + Base::Rotation rot = plm.getRotation(); + Base::Vector3d normal(0, 0, 1); + rot.multVec(normal, normal); + gp_Pln plane(gp_Pnt(base.x, base.y, base.z), gp_Dir(normal.x, normal.y, normal.z)); + BRepBuilderAPI_MakeFace fBuilder(plane); + if (!fBuilder.IsDone()) + throw Base::RuntimeError( + "Sketcher: addExternal(): Failed to build face from App::Plane"); + + TopoDS_Face f = TopoDS::Face(fBuilder.Shape()); + refSubShape = f; + } + else { + throw Base::TypeError( + "Datum feature type is not yet supported as external geometry for a sketch"); } - } - else if (Obj->isDerivedFrom()) { - auto* pl = static_cast(Obj); - Base::Placement plm = pl->Placement.getValue(); - Base::Vector3d base = plm.getPosition(); - Base::Rotation rot = plm.getRotation(); - Base::Vector3d normal(0, 0, 1); - rot.multVec(normal, normal); - gp_Pln plane(gp_Pnt(base.x, base.y, base.z), gp_Dir(normal.x, normal.y, normal.z)); - BRepBuilderAPI_MakeFace fBuilder(plane); - if (!fBuilder.IsDone()) - throw Base::RuntimeError( - "Sketcher: addExternal(): Failed to build face from App::Plane"); - TopoDS_Face f = TopoDS::Face(fBuilder.Shape()); - refSubShape = f; - } - else { - throw Base::TypeError( - "Datum feature type is not yet supported as external geometry for a sketch"); - } + if (projection) { + switch (refSubShape.ShapeType()) { + case TopAbs_FACE: { + const TopoDS_Face& face = TopoDS::Face(refSubShape); + BRepAdaptor_Surface surface(face); + if (surface.GetType() == GeomAbs_Plane) { + // Check that the plane is perpendicular to the sketch plane + Geom_Plane plane = surface.Plane(); + gp_Dir dnormal = plane.Axis().Direction(); + gp_Dir snormal = sketchPlane.Axis().Direction(); - switch (refSubShape.ShapeType()) { - case TopAbs_FACE: { - const TopoDS_Face& face = TopoDS::Face(refSubShape); - BRepAdaptor_Surface surface(face); - if (surface.GetType() == GeomAbs_Plane) { - // Check that the plane is perpendicular to the sketch plane - Geom_Plane plane = surface.Plane(); - gp_Dir dnormal = plane.Axis().Direction(); - gp_Dir snormal = sketchPlane.Axis().Direction(); + // Extract all edges from the face + TopExp_Explorer edgeExp; + for (edgeExp.Init(face, TopAbs_EDGE); edgeExp.More(); edgeExp.Next()) { + TopoDS_Edge edge = TopoDS::Edge(edgeExp.Current()); + // Process each edge + processEdge(edge, geos, gPlane, invPlm, mov, sketchPlane, invRot, sketchAx3, aProjFace); + } - // Extract all edges from the face - TopExp_Explorer edgeExp; - for (edgeExp.Init(face, TopAbs_EDGE); edgeExp.More(); edgeExp.Next()) { - TopoDS_Edge edge = TopoDS::Edge(edgeExp.Current()); - // Process each edge - processEdge(edge, geos, gPlane, invPlm, mov, sketchPlane, invRot, sketchAx3, aProjFace); - } - - if (fabs(dnormal.Angle(snormal) - M_PI_2) < Precision::Confusion()) { - // The face is normal to the sketch plane - // We don't want to keep the projection of all the edges of the face. - // We need a single line that goes from min to max of all the projections. - bool initialized = false; - Base::Vector3d start, end; - // Lambda to determine if a point should replace start or end - auto updateExtremes = [&](const Base::Vector3d& point) { - if ((point - start).Length() < (point - end).Length()) { - // `point` is closer to `start` than `end`, check if it's further out than `start` - if ((point - end).Length() > (end - start).Length()) { - start = point; + if (fabs(dnormal.Angle(snormal) - M_PI_2) < Precision::Confusion()) { + // The face is normal to the sketch plane + // We don't want to keep the projection of all the edges of the face. + // We need a single line that goes from min to max of all the projections. + bool initialized = false; + Base::Vector3d start, end; + // Lambda to determine if a point should replace start or end + auto updateExtremes = [&](const Base::Vector3d& point) { + if ((point - start).Length() < (point - end).Length()) { + // `point` is closer to `start` than `end`, check if it's further out than `start` + if ((point - end).Length() > (end - start).Length()) { + start = point; + } } + else { + // `point` is closer to `end`, check if it's further out than `end` + if ((point - start).Length() > (end - start).Length()) { + end = point; + } + } + }; + for (auto& geo : geos) { + auto* line = dynamic_cast(geo.get()); + if (!line) { + // The face being normal to the sketch, we should have + // only lines. This is just a fail-safe in case there's a + // straight bspline or something like this. + continue; + } + if (!initialized) { + start = line->getStartPoint(); + end = line->getEndPoint(); + initialized = true; + continue; + } + + updateExtremes(line->getStartPoint()); + updateExtremes(line->getEndPoint()); + } + if (initialized) { + auto* unifiedLine = new Part::GeomLineSegment(); + unifiedLine->setPoints(start, end); + geos.clear(); // Clear other segments + geos.emplace_back(unifiedLine); } else { - // `point` is closer to `end`, check if it's further out than `end` - if ((point - start).Length() > (end - start).Length()) { - end = point; + // In case we have not initialized, perhaps the projections were + // only straight bsplines. + // Then we use the old method that will give a line with 20000 length: + // Get vector that is normal to both sketch plane normal and plane normal. + // This is the line's direction + gp_Dir lnormal = dnormal.Crossed(snormal); + BRepBuilderAPI_MakeEdge builder(gp_Lin(plane.Location(), lnormal)); + builder.Build(); + if (builder.IsDone()) { + const TopoDS_Edge& edge = TopoDS::Edge(builder.Shape()); + BRepAdaptor_Curve curve(edge); + if (curve.GetType() == GeomAbs_Line) { + geos.emplace_back(projectLine(curve, gPlane, invPlm)); + } } } - }; - for (auto& geo : geos) { - auto* line = dynamic_cast(geo.get()); - if (!line) { - continue; // we should have only lines - } - if (!initialized) { - start = line->getStartPoint(); - end = line->getEndPoint(); - initialized = true; - continue; - } - - updateExtremes(line->getStartPoint()); - updateExtremes(line->getEndPoint()); - } - - auto* unifiedLine = new Part::GeomLineSegment(); - unifiedLine->setPoints(start, end); - geos.clear(); // Clear other segments - geos.emplace_back(unifiedLine); - } - } - else { - std::vector res = projectShape(face, sketchAx3); - for (auto& resShape : res) { - TopExp_Explorer explorer(resShape, TopAbs_EDGE); - while (explorer.More()) { - TopoDS_Edge projEdge = TopoDS::Edge(explorer.Current()); - processEdge2(projEdge, geos); - explorer.Next(); } } + else { + std::vector res = projectShape(face, sketchAx3); + for (auto& resShape : res) { + TopExp_Explorer explorer(resShape, TopAbs_EDGE); + while (explorer.More()) { + TopoDS_Edge projEdge = TopoDS::Edge(explorer.Current()); + processEdge2(projEdge, geos); + explorer.Next(); + } + } + } + } break; + case TopAbs_EDGE: { + const TopoDS_Edge& edge = TopoDS::Edge(refSubShape); + processEdge(edge, geos, gPlane, invPlm, mov, sketchPlane, invRot, sketchAx3, aProjFace); + } break; + case TopAbs_VERTEX: { + importVertex(refSubShape); + } break; + default: + throw Base::TypeError("Unknown type of geometry"); + break; + } + if (beingCreated && !extToAdd->intersection) { + // We are adding the projections, so we need to initialize those + for (auto& geo : geos) { + auto egf = ExternalGeometryFacade::getFacade(geo.get()); + egf->setFlag(ExternalGeometryExtension::Defining, extToAdd->defining); + } } - } break; - case TopAbs_EDGE: { - const TopoDS_Edge& edge = TopoDS::Edge(refSubShape); - processEdge(edge, geos, gPlane, invPlm, mov, sketchPlane, invRot, sketchAx3, aProjFace); - } break; - case TopAbs_VERTEX: { - gp_Pnt P = BRep_Tool::Pnt(TopoDS::Vertex(refSubShape)); - GeomAPI_ProjectPointOnSurf proj(P, gPlane); - P = proj.NearestPoint(); - Base::Vector3d p(P.X(), P.Y(), P.Z()); - invPlm.multVec(p, p); - - auto* point = new Part::GeomPoint(p); - GeometryFacade::setConstruction(point, true); - geos.emplace_back(point); - } break; - default: - throw Base::TypeError("Unknown type of geometry"); - break; } + int projSize = geos.size(); - if (intersection && (refSubShape.ShapeType() == TopAbs_EDGE - || refSubShape.ShapeType() == TopAbs_FACE)) - { + if (intersection) { FCBRepAlgoAPI_Section maker(refSubShape, sketchPlane); maker.Approximation(Standard_True); if (!maker.IsDone()) - FC_THROWM(Base::CADKernelError,"Failed to get intersection"); + FC_THROWM(Base::CADKernelError, "Failed to get intersection"); Part::TopoShape intersectionShape(maker.Shape()); auto edges = intersectionShape.getSubTopoShapes(TopAbs_EDGE); + for (const auto& s : edges) { + TopoDS_Edge edge = TopoDS::Edge(s.getShape()); + processEdge(edge, geos, gPlane, invPlm, mov, sketchPlane, invRot, sketchAx3, aProjFace); + } // Section of some face (e.g. sphere) produce more than one arcs // from the same circle. So we try to fit the arcs with a single // circle/arc. @@ -9190,6 +9284,17 @@ void SketchObject::rebuildExternalGeometry(bool defining, bool addIntersection) } } } + for (const auto& s : intersectionShape.getSubShapes(TopAbs_VERTEX, TopAbs_EDGE)) { + importVertex(s); + } + + if (beingCreated && extToAdd->intersection) { + // We are adding the projections, so we need to initialize those + for (size_t i = projSize; i < geos.size(); ++i) { + auto egf = ExternalGeometryFacade::getFacade(geos[i].get()); + egf->setFlag(ExternalGeometryExtension::Defining, extToAdd->defining); + } + } } } catch (Base::Exception &e) { @@ -9209,25 +9314,18 @@ void SketchObject::rebuildExternalGeometry(bool defining, bool addIntersection) << getFullName() << ": " << key << std::endl << "Unknown exception"); continue; } - if(geos.empty()) + if (geos.empty()) { continue; + } if(!refSet.emplace(key).second) { FC_WARN("Duplicated external reference in " << getFullName() << ": " << key); continue; } - if (intersection) { - for(auto &geo : geos) { - auto egf = ExternalGeometryFacade::getFacade(geo.get()); - egf->setFlag(ExternalGeometryExtension::Defining, defining); - } - } else if (defining && i+1==(int)Objects.size()) { - for(auto &geo : geos) - ExternalGeometryFacade::getFacade(geo.get())->setFlag( - ExternalGeometryExtension::Defining); - } - for(auto &geo : geos) + + for (auto& geo : geos) { ExternalGeometryFacade::getFacade(geo.get())->setRef(key); + } newGeos.push_back(std::move(geos)); } @@ -9308,8 +9406,9 @@ void SketchObject::rebuildExternalGeometry(bool defining, bool addIntersection) solverNeedsUpdate=true; Constraints.acceptGeometry(getCompleteGeometry()); - if(hasError && this->isRecomputing()) + if (hasError && this->isRecomputing()) { throw Base::RuntimeError("Missing external geometry reference"); + } } void SketchObject::fixExternalGeometry(const std::vector &geoIds) { diff --git a/src/Mod/Sketcher/App/SketchObject.h b/src/Mod/Sketcher/App/SketchObject.h index dd32a9a936..7ad241928b 100644 --- a/src/Mod/Sketcher/App/SketchObject.h +++ b/src/Mod/Sketcher/App/SketchObject.h @@ -46,6 +46,20 @@ namespace Sketcher class SketchAnalysis; +struct ExternalToAdd +{ + App::DocumentObject* obj; + std::string subname; + bool defining; + bool intersection; +}; +enum class ExtType +{ + Projection, + Intersection, + Both +}; + class SketcherExport SketchObject: public Part::Part2DObject { typedef Part::Part2DObject inherited; @@ -68,6 +82,7 @@ public: Part ::PropertyGeometryList Geometry; Sketcher::PropertyConstraintList Constraints; App ::PropertyLinkSubList ExternalGeometry; + App::PropertyIntegerList ExternalTypes; App ::PropertyLinkListHidden Exports; Part ::PropertyGeometryList ExternalGeo; App ::PropertyBool FullyConstrained; @@ -231,7 +246,9 @@ public: return ExternalGeo.getValues(); } /// rebuilds external geometry (projection onto the sketch plane) - void rebuildExternalGeometry(bool defining = false, bool intersection = false); + // It uses std::optional because this function is actually used to both recompute external + // geometries but also to add new external geometries. Ideally this should be refactored. + void rebuildExternalGeometry(std::optional extToAdd = std::nullopt); /// returns the number of external Geometry entities int getExternalGeometryCount() const { diff --git a/src/Mod/Sketcher/App/SketchObjectPy.xml b/src/Mod/Sketcher/App/SketchObjectPy.xml index 06cdfec6b0..b47a995cbc 100644 --- a/src/Mod/Sketcher/App/SketchObjectPy.xml +++ b/src/Mod/Sketcher/App/SketchObjectPy.xml @@ -253,12 +253,14 @@ carbonCopy(objName:str, asConstruction=True) Add a link to an external geometry. -addExternal(objName:str, subName:str) +addExternal(objName:str, subName:str, bool:defining, bool:intersection) Args: objName: The name of the document object to reference. subName: The name of the sub-element of the object's shape to link as "external geometry". + defining: Should the external edges be defining or construction? + intersection: Should the external edges be projections or intersections? diff --git a/src/Mod/Sketcher/App/SketchObjectPyImp.cpp b/src/Mod/Sketcher/App/SketchObjectPyImp.cpp index e53c08095f..67550b5cb5 100644 --- a/src/Mod/Sketcher/App/SketchObjectPyImp.cpp +++ b/src/Mod/Sketcher/App/SketchObjectPyImp.cpp @@ -557,19 +557,35 @@ PyObject* SketchObjectPy::addExternal(PyObject* args) { char* ObjectName; char* SubName; - PyObject* defining; // this is an optional argument default false + PyObject* defining; // this is an optional argument default false + PyObject* intersection; // this is an optional argument default false bool isDefining; - if (!PyArg_ParseTuple(args, "ssO!", &ObjectName, &SubName, &PyBool_Type, &defining)) { - PyErr_Clear(); - if (!PyArg_ParseTuple(args, "ss", &ObjectName, &SubName)) { - return nullptr; + bool isIntersection; + if (!PyArg_ParseTuple(args, + "ssO!O!", + &ObjectName, + &SubName, + &PyBool_Type, + &defining, + &PyBool_Type, + &intersection)) { + if (!PyArg_ParseTuple(args, "ssO!", &ObjectName, &SubName, &PyBool_Type, &defining)) { + PyErr_Clear(); + if (!PyArg_ParseTuple(args, "ss", &ObjectName, &SubName)) { + return nullptr; + } + else { + isDefining = false; + } } else { - isDefining = false; + isDefining = Base::asBoolean(defining); } + isIntersection = false; } else { isDefining = Base::asBoolean(defining); + isIntersection = Base::asBoolean(intersection); } // get the target object for the external link @@ -590,7 +606,7 @@ PyObject* SketchObjectPy::addExternal(PyObject* args) } // add the external - if (skObj->addExternal(Obj, SubName, isDefining) < 0) { + if (skObj->addExternal(Obj, SubName, isDefining, isIntersection) < 0) { std::stringstream str; str << "Not able to add external shape element " << SubName; PyErr_SetString(PyExc_ValueError, str.str().c_str()); diff --git a/src/Mod/Sketcher/Gui/CommandAlterGeometry.cpp b/src/Mod/Sketcher/Gui/CommandAlterGeometry.cpp index 72db9b24d7..2a0c880c31 100644 --- a/src/Mod/Sketcher/Gui/CommandAlterGeometry.cpp +++ b/src/Mod/Sketcher/Gui/CommandAlterGeometry.cpp @@ -115,7 +115,9 @@ CmdSketcherToggleConstruction::CmdSketcherToggleConstruction() rcCmdMgr.addCommandMode("ToggleConstruction", "Sketcher_CreatePeriodicBSplineByInterpolation"); rcCmdMgr.addCommandMode("ToggleConstruction", "Sketcher_CompCreateBSpline"); rcCmdMgr.addCommandMode("ToggleConstruction", "Sketcher_CarbonCopy"); + rcCmdMgr.addCommandMode("ToggleConstruction", "Sketcher_CompExternal"); rcCmdMgr.addCommandMode("ToggleConstruction", "Sketcher_External"); + rcCmdMgr.addCommandMode("ToggleConstruction", "Sketcher_Intersection"); rcCmdMgr.addCommandMode("ToggleConstruction", "Sketcher_ToggleConstruction"); } diff --git a/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp b/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp index 9f9986080a..c7d4649098 100644 --- a/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp +++ b/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp @@ -1434,7 +1434,63 @@ public: } }; -// ====================================================================================== +// Group for external tools ============================================= + +class CmdSketcherCompExternal: public Gui::GroupCommand +{ +public: + CmdSketcherCompExternal() + : GroupCommand("Sketcher_CompExternal") + { + sAppModule = "Sketcher"; + sGroup = "Sketcher"; + sMenuText = QT_TR_NOOP("Create external"); + sToolTipText = QT_TR_NOOP("Create external edges linked to external geometries."); + sWhatsThis = "Sketcher_CompExternal"; + sStatusTip = sToolTipText; + eType = ForEdit; + + setCheckable(false); + + addCommand("Sketcher_External"); + addCommand("Sketcher_Intersection"); + } + + void updateAction(int mode) override + { + Gui::ActionGroup* pcAction = qobject_cast(getAction()); + if (!pcAction) { + return; + } + + QList al = pcAction->actions(); + int index = pcAction->property("defaultAction").toInt(); + switch (static_cast(mode)) { + case GeometryCreationMode::Normal: + al[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_External")); + al[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Intersection")); + getAction()->setIcon(al[index]->icon()); + break; + case GeometryCreationMode::Construction: + al[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_External_Constr")); + al[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Intersection_Constr")); + getAction()->setIcon(al[index]->icon()); + break; + } + } + + const char* className() const override + { + return "CmdSketcherCompExternal"; + } + + bool isActive() override + { + return isCommandActive(getActiveGuiDocument()); + } +}; + +// Externals - Projection ================================================================== DEF_STD_CMD_AU(CmdSketcherExternal) @@ -1443,8 +1499,10 @@ CmdSketcherExternal::CmdSketcherExternal() { sAppModule = "Sketcher"; sGroup = "Sketcher"; - sMenuText = QT_TR_NOOP("Create external geometry"); - sToolTipText = QT_TR_NOOP("Create an edge linked to an external geometry"); + sMenuText = QT_TR_NOOP("Create external projection geometry"); + sToolTipText = QT_TR_NOOP("Create the projection edges of an external geometry.\n" + "External edges can be either defining or construction geometries.\n" + "You can use the toggle construction tool."); sWhatsThis = "Sketcher_External"; sStatusTip = sToolTipText; sPixmap = "Sketcher_External"; @@ -1465,6 +1523,39 @@ bool CmdSketcherExternal::isActive() return isCommandActive(getActiveGuiDocument()); } +// Externals - Intersection ================================================================== + +DEF_STD_CMD_AU(CmdSketcherIntersection) + +CmdSketcherIntersection::CmdSketcherIntersection() + : Command("Sketcher_Intersection") +{ + sAppModule = "Sketcher"; + sGroup = "Sketcher"; + sMenuText = QT_TR_NOOP("Create external intersection geometry"); + sToolTipText = + QT_TR_NOOP("Create the intersection edges of an external geometry with the sketch plane.\n" + "External edges can be either defining or construction geometries.\n" + "You can use the toggle construction tool."); + sWhatsThis = "Sketcher_Intersection"; + sStatusTip = sToolTipText; + sPixmap = "Sketcher_Intersection"; + sAccel = "G, I"; + eType = ForEdit; +} + +CONSTRUCTION_UPDATE_ACTION(CmdSketcherIntersection, "Sketcher_Intersection") + +void CmdSketcherIntersection::activated(int iMsg) +{ + ActivateHandler(getActiveGuiDocument(), std::make_unique(true)); +} + +bool CmdSketcherIntersection::isActive(void) +{ + return isCommandActive(getActiveGuiDocument()); +} + // ====================================================================================== DEF_STD_CMD_AU(CmdSketcherCarbonCopy) @@ -2069,6 +2160,8 @@ void CreateSketcherCommandsCreateGeo() rcCmdMgr.addCommand(new CmdSketcherSplit()); rcCmdMgr.addCommand(new CmdSketcherCompCurveEdition()); rcCmdMgr.addCommand(new CmdSketcherExternal()); + rcCmdMgr.addCommand(new CmdSketcherIntersection()); + rcCmdMgr.addCommand(new CmdSketcherCompExternal()); rcCmdMgr.addCommand(new CmdSketcherCarbonCopy()); rcCmdMgr.addCommand(new CmdSketcherCompLine()); } diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerExternal.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerExternal.h index cc57af0df5..5c0613a1f8 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchHandlerExternal.h +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerExternal.h @@ -116,7 +116,9 @@ public: class DrawSketchHandlerExternal: public DrawSketchHandler { public: - DrawSketchHandlerExternal() = default; + DrawSketchHandlerExternal(bool intersection = false) + : intersection(intersection) + {} ~DrawSketchHandlerExternal() override { Gui::Selection().rmvSelectionGate(); @@ -163,10 +165,11 @@ public: Gui::Command::openCommand( QT_TRANSLATE_NOOP("Command", "Add external geometry")); Gui::cmdAppObjectArgs(sketchgui->getObject(), - "addExternal(\"%s\",\"%s\", %s)", + "addExternal(\"%s\",\"%s\", %s, %s)", msg.pObjectName, msg.pSubName, - isConstructionMode() ? "False" : "True"); + isConstructionMode() ? "False" : "True", + intersection ? "True" : "False"); Gui::Command::commitCommand(); @@ -214,6 +217,10 @@ private: QString getCrosshairCursorSVGName() const override { + if (intersection) { + return QString::fromLatin1("Sketcher_Pointer_External_Intersection"); + } + return QString::fromLatin1("Sketcher_Pointer_External"); } @@ -222,6 +229,8 @@ private: Q_UNUSED(sketchgui); setAxisPickStyle(true); } + + bool intersection; }; diff --git a/src/Mod/Sketcher/Gui/Resources/Sketcher.qrc b/src/Mod/Sketcher/Gui/Resources/Sketcher.qrc index 037df41365..90bbeaa296 100644 --- a/src/Mod/Sketcher/Gui/Resources/Sketcher.qrc +++ b/src/Mod/Sketcher/Gui/Resources/Sketcher.qrc @@ -200,6 +200,8 @@ icons/geometry/Sketcher_Extend.svg icons/geometry/Sketcher_External.svg icons/geometry/Sketcher_External_Constr.svg + icons/geometry/Sketcher_Intersection.svg + icons/geometry/Sketcher_Intersection_Constr.svg icons/geometry/Sketcher_Split.svg icons/geometry/Sketcher_ToggleConstruction.svg icons/geometry/Sketcher_ToggleConstruction_Constr.svg @@ -251,6 +253,7 @@ icons/pointers/Sketcher_Pointer_Create_Symmetry.svg icons/pointers/Sketcher_Pointer_Extension.svg icons/pointers/Sketcher_Pointer_External.svg + icons/pointers/Sketcher_Pointer_External_Intersection.svg icons/pointers/Sketcher_Pointer_Heptagon.svg icons/pointers/Sketcher_Pointer_Hexagon.svg icons/pointers/Sketcher_Pointer_InsertKnot.svg diff --git a/src/Mod/Sketcher/Gui/Resources/icons/geometry/Sketcher_Intersection.svg b/src/Mod/Sketcher/Gui/Resources/icons/geometry/Sketcher_Intersection.svg new file mode 100644 index 0000000000..b3f6c993ae --- /dev/null +++ b/src/Mod/Sketcher/Gui/Resources/icons/geometry/Sketcher_Intersection.svg @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + [maxwxyz] + + + https://www.freecad.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_CreateArc.svg + + + FreeCAD LGPL2+ + + + 2023-12-19 + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Sketcher/Gui/Resources/icons/geometry/Sketcher_Intersection_Constr.svg b/src/Mod/Sketcher/Gui/Resources/icons/geometry/Sketcher_Intersection_Constr.svg new file mode 100644 index 0000000000..29e130e4ca --- /dev/null +++ b/src/Mod/Sketcher/Gui/Resources/icons/geometry/Sketcher_Intersection_Constr.svg @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + [maxwxyz] + + + https://www.freecad.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_CreateArc.svg + + + FreeCAD LGPL2+ + + + 2023-12-19 + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Sketcher/Gui/Resources/icons/pointers/Sketcher_Pointer_External_Intersection.svg b/src/Mod/Sketcher/Gui/Resources/icons/pointers/Sketcher_Pointer_External_Intersection.svg new file mode 100644 index 0000000000..b7124ee893 --- /dev/null +++ b/src/Mod/Sketcher/Gui/Resources/icons/pointers/Sketcher_Pointer_External_Intersection.svg @@ -0,0 +1,101 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Sketcher/Gui/Workbench.cpp b/src/Mod/Sketcher/Gui/Workbench.cpp index d5fa37c59c..e02ccb2360 100644 --- a/src/Mod/Sketcher/Gui/Workbench.cpp +++ b/src/Mod/Sketcher/Gui/Workbench.cpp @@ -538,6 +538,7 @@ inline void SketcherAddWorkbenchTools(Gui::MenuItem& consaccel) SketcherAddWorkspaceFillets(consaccel); SketcherAddWorkspaceCurveEdition(consaccel); consaccel << "Sketcher_External" + << "Sketcher_Intersection" << "Sketcher_CarbonCopy" << "Separator" << "Sketcher_SelectOrigin" @@ -564,7 +565,7 @@ inline void SketcherAddWorkbenchTools(Gui::ToolBarItem& consac { SketcherAddWorkspaceFillets(consaccel); SketcherAddWorkspaceCurveEdition(consaccel); - consaccel << "Sketcher_External" + consaccel << "Sketcher_CompExternal" << "Sketcher_CarbonCopy" << "Separator" << "Sketcher_Translate"